<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: FlatlinerAtheist | CLI-NERDS</title>
    <description>The latest articles on DEV Community by FlatlinerAtheist | CLI-NERDS (@clinerds).</description>
    <link>https://dev.to/clinerds</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4189900%2F91917414-3cb4-4a8a-9a53-8c115a821b0b.png</url>
      <title>DEV Community: FlatlinerAtheist | CLI-NERDS</title>
      <link>https://dev.to/clinerds</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/clinerds"/>
    <language>en</language>
    <item>
      <title>I Replaced Google Photos With a $60 Board in My Closet</title>
      <dc:creator>FlatlinerAtheist | CLI-NERDS</dc:creator>
      <pubDate>Sun, 18 Oct 2026 06:23:07 +0000</pubDate>
      <link>https://dev.to/clinerds/i-replaced-google-photos-with-a-60-board-in-my-closet-501l</link>
      <guid>https://dev.to/clinerds/i-replaced-google-photos-with-a-60-board-in-my-closet-501l</guid>
      <description>&lt;p&gt;Google Photos was great until it wasn't. The free tier died years ago, my "Storage almost full" notification had become a weekly ritual, and the idea of paying rent forever to access my own memories started to genuinely annoy me. So last weekend I did what any reasonable person with a spare ARM board and trust issues would do: I built my own Google Photos.&lt;/p&gt;

&lt;p&gt;The whole thing runs on a 4-core ARM board with 8 GB of RAM sitting in my closet. Photos auto-upload from my phone, I get a timeline view, a map, sharing links, the works. Total cost of ownership: the electricity a 5-watt board draws. Here's exactly how I did it — the native way &lt;em&gt;and&lt;/em&gt; the Docker way — with the mistakes included, because that's where the learning is.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3y0iohyqcd65nthfeeva.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3y0iohyqcd65nthfeeva.webp" alt="Hero — the CLINERDS cloud" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Too long; just run it:&lt;/strong&gt; the whole native setup is also packaged as an interactive script — it asks you a handful of questions (domain, nginx or apache, postgres or mariadb, data dir) and does the rest. Grab &lt;a href="https://clinerds.com/covers/nextcloud/setup-nextcloud.sh" rel="noopener noreferrer"&gt;&lt;code&gt;setup-nextcloud.sh&lt;/code&gt;&lt;/a&gt;, read it (always read scripts before piping them into your life), then &lt;code&gt;chmod +x setup-nextcloud.sh &amp;amp;&amp;amp; ./setup-nextcloud.sh&lt;/code&gt;. The post below is the "what and why" behind everything the script does.&lt;/p&gt;

&lt;h2&gt;
  
  
  The stack (and why)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Nextcloud&lt;/strong&gt; — the obvious pick. It's the only self-hosted suite with a mature Android app that does camera auto-upload properly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;nginx + PHP-FPM&lt;/strong&gt; instead of Apache. Nextcloud ships &lt;code&gt;.htaccess&lt;/code&gt; files and "officially" prefers Apache, but their docs publish a full nginx config, and nginx uses a fraction of the RAM. On a small board that matters.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PostgreSQL&lt;/strong&gt; instead of MySQL/MariaDB. Nextcloud's own docs recommend it, file locking behaves better under concurrency, and you skip the whole &lt;code&gt;utf8mb4&lt;/code&gt; / row-format dance MariaDB makes you do.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A 128 GB USB drive&lt;/strong&gt; for data. SD cards die under database + thumbnail workloads. Don't store your photos on the boot card.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cloudflare Tunnel&lt;/strong&gt; for HTTPS and remote access. No port forwarding, no exposing my home IP, free valid TLS.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Part 1: The native install
&lt;/h2&gt;

&lt;p&gt;This is the path I actually ran. Everything below is copy-paste real.&lt;/p&gt;

&lt;h3&gt;
  
  
  Packages
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; nginx postgresql redis-server &lt;span class="se"&gt;\&lt;/span&gt;
  php-fpm php-pgsql php-gd php-curl php-zip php-xml php-mbstring &lt;span class="se"&gt;\&lt;/span&gt;
  php-intl php-bcmath php-gmp php-imagick php-redis php-apcu &lt;span class="se"&gt;\&lt;/span&gt;
  unzip ffmpeg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(If you're team MySQL: swap &lt;code&gt;postgresql&lt;/code&gt; for &lt;code&gt;mariadb-server&lt;/code&gt; and &lt;code&gt;php-pgsql&lt;/code&gt; for &lt;code&gt;php-mysql&lt;/code&gt;. Everything else is identical. I'll show both database setups below.)&lt;/p&gt;

&lt;p&gt;First gotcha, straight from my terminal history: on a freshly booted Ubuntu, &lt;code&gt;unattended-upgrades&lt;/code&gt; holds the dpkg lock hostage for a few minutes and apt just fails. Either wait it out or:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;fuser /var/lib/dpkg/lock-frontend &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null 2&amp;gt;&amp;amp;1&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do &lt;/span&gt;&lt;span class="nb"&gt;sleep &lt;/span&gt;10&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Database
&lt;/h3&gt;

&lt;p&gt;PostgreSQL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;sudo&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="n"&gt;postgres&lt;/span&gt; &lt;span class="n"&gt;psql&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;USER&lt;/span&gt; &lt;span class="n"&gt;nextcloud&lt;/span&gt; &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;PASSWORD&lt;/span&gt; &lt;span class="s1"&gt;'pick-a-real-password'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;DATABASE&lt;/span&gt; &lt;span class="n"&gt;nextcloud&lt;/span&gt; &lt;span class="k"&gt;OWNER&lt;/span&gt; &lt;span class="n"&gt;nextcloud&lt;/span&gt; &lt;span class="k"&gt;TEMPLATE&lt;/span&gt; &lt;span class="n"&gt;template0&lt;/span&gt; &lt;span class="k"&gt;ENCODING&lt;/span&gt; &lt;span class="s1"&gt;'UTF8'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or MariaDB, if that's your religion:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;sudo&lt;/span&gt; &lt;span class="n"&gt;mariadb&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;DATABASE&lt;/span&gt; &lt;span class="n"&gt;nextcloud&lt;/span&gt; &lt;span class="nb"&gt;CHARACTER&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;utf8mb4&lt;/span&gt; &lt;span class="k"&gt;COLLATE&lt;/span&gt; &lt;span class="n"&gt;utf8mb4_general_ci&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;USER&lt;/span&gt; &lt;span class="s1"&gt;'nextcloud'&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="s1"&gt;'localhost'&lt;/span&gt; &lt;span class="n"&gt;IDENTIFIED&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="s1"&gt;'pick-a-real-password'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt; &lt;span class="k"&gt;PRIVILEGES&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;nextcloud&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="s1"&gt;'nextcloud'&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="s1"&gt;'localhost'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;FLUSH&lt;/span&gt; &lt;span class="k"&gt;PRIVILEGES&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One thing to check before you pick a PHP version: Nextcloud is picky about it. There's a &lt;a href="https://github.com/nextcloud/server/wiki/Releases-and-PHP-versions" rel="noopener noreferrer"&gt;compatibility matrix on their wiki&lt;/a&gt; — check it &lt;em&gt;before&lt;/em&gt; apt installs whatever your distro's default is. My Ubuntu shipped PHP 8.5, which Nextcloud 34/35 supports. Lucky. An older release would have needed a PPA dance.&lt;/p&gt;

&lt;h3&gt;
  
  
  The data directory (on the USB drive)
&lt;/h3&gt;

&lt;p&gt;My drive already had a partition, so I just mounted it. If yours is fresh, format it first (&lt;code&gt;sudo mkfs.ext4 -L nextcloud-data /dev/sda1&lt;/code&gt; — &lt;strong&gt;check the device name three times&lt;/strong&gt; before running that).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /mnt/usb
&lt;span class="nb"&gt;sudo &lt;/span&gt;mount /dev/sda1 /mnt/usb
&lt;span class="nb"&gt;sudo mkdir&lt;/span&gt; /mnt/usb/nextcloud-data
&lt;span class="nb"&gt;sudo chown &lt;/span&gt;www-data:www-data /mnt/usb/nextcloud-data
&lt;span class="nb"&gt;sudo chmod &lt;/span&gt;750 /mnt/usb/nextcloud-data
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make it survive reboots. The &lt;code&gt;nofail&lt;/code&gt; flag is important — without it, the board won't boot if the drive is unplugged:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"UUID=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;blkid &lt;span class="nt"&gt;-s&lt;/span&gt; UUID &lt;span class="nt"&gt;-o&lt;/span&gt; value /dev/sda1&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt; /mnt/usb ext4 defaults,nofail 0 2"&lt;/span&gt; | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; /etc/fstab
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Nextcloud itself
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; /tmp
wget https://download.nextcloud.com/server/releases/latest.zip
&lt;span class="nb"&gt;sudo &lt;/span&gt;unzip &lt;span class="nt"&gt;-q&lt;/span&gt; latest.zip &lt;span class="nt"&gt;-d&lt;/span&gt; /var/www/
&lt;span class="nb"&gt;sudo chown&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; www-data:www-data /var/www/nextcloud
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;PHP tuning — &lt;code&gt;/etc/php/8.5/fpm/conf.d/99-nextcloud.ini&lt;/code&gt; (drop the same file into &lt;code&gt;cli/conf.d/&lt;/code&gt; too, or &lt;code&gt;occ&lt;/code&gt; will behave differently from the web app and confuse you later):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="py"&gt;memory_limit&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;512M&lt;/span&gt;
&lt;span class="py"&gt;upload_max_filesize&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;10G&lt;/span&gt;
&lt;span class="py"&gt;post_max_size&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;10G&lt;/span&gt;
&lt;span class="py"&gt;max_execution_time&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;360&lt;/span&gt;
&lt;span class="py"&gt;output_buffering&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;Off&lt;/span&gt;

&lt;span class="py"&gt;opcache.enable&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;1&lt;/span&gt;
&lt;span class="py"&gt;opcache.interned_strings_buffer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;16&lt;/span&gt;
&lt;span class="py"&gt;opcache.max_accelerated_files&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;10000&lt;/span&gt;
&lt;span class="py"&gt;opcache.memory_consumption&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;256&lt;/span&gt;
&lt;span class="py"&gt;opcache.save_comments&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;1&lt;/span&gt;
&lt;span class="py"&gt;opcache.revalidate_freq&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;60&lt;/span&gt;

&lt;span class="py"&gt;apc.enable_cli&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The nginx vhost — &lt;code&gt;/etc/nginx/sites-available/nextcloud&lt;/code&gt;. This is adapted from Nextcloud's official nginx template; the non-obvious bits are the &lt;code&gt;.well-known&lt;/code&gt; redirects (CalDAV/CardDAV break without them) and the &lt;code&gt;front_controller_active&lt;/code&gt; param:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;upstream&lt;/span&gt; &lt;span class="s"&gt;php-handler&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;server&lt;/span&gt; &lt;span class="s"&gt;unix:/run/php/php8.5-fpm.sock&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;listen&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;listen&lt;/span&gt; &lt;span class="s"&gt;[::]:80&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;server_name&lt;/span&gt; &lt;span class="s"&gt;cloud.example.com&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;root&lt;/span&gt; &lt;span class="n"&gt;/var/www/nextcloud&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;server_tokens&lt;/span&gt; &lt;span class="no"&gt;off&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;client_max_body_size&lt;/span&gt; &lt;span class="mi"&gt;10G&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;client_body_timeout&lt;/span&gt; &lt;span class="s"&gt;360s&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;add_header&lt;/span&gt; &lt;span class="s"&gt;Strict-Transport-Security&lt;/span&gt; &lt;span class="s"&gt;"max-age=15552000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="kn"&gt;includeSubDomains"&lt;/span&gt; &lt;span class="s"&gt;always&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;add_header&lt;/span&gt; &lt;span class="s"&gt;X-Content-Type-Options&lt;/span&gt;     &lt;span class="s"&gt;"nosniff"&lt;/span&gt;      &lt;span class="s"&gt;always&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;add_header&lt;/span&gt; &lt;span class="s"&gt;X-Frame-Options&lt;/span&gt;            &lt;span class="s"&gt;"SAMEORIGIN"&lt;/span&gt;   &lt;span class="s"&gt;always&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;add_header&lt;/span&gt; &lt;span class="s"&gt;Referrer-Policy&lt;/span&gt;            &lt;span class="s"&gt;"no-referrer"&lt;/span&gt;  &lt;span class="s"&gt;always&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;fastcgi_hide_header&lt;/span&gt; &lt;span class="s"&gt;X-Powered-By&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;index&lt;/span&gt; &lt;span class="s"&gt;index.php&lt;/span&gt; &lt;span class="s"&gt;index.html&lt;/span&gt; &lt;span class="n"&gt;/index.php&lt;/span&gt;&lt;span class="nv"&gt;$request_uri&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;/robots.txt&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kn"&gt;allow&lt;/span&gt; &lt;span class="s"&gt;all&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="kn"&gt;log_not_found&lt;/span&gt; &lt;span class="no"&gt;off&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="kn"&gt;access_log&lt;/span&gt; &lt;span class="no"&gt;off&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="s"&gt;^~&lt;/span&gt; &lt;span class="n"&gt;/.well-known&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;/.well-known/carddav&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kn"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;301&lt;/span&gt; &lt;span class="n"&gt;/remote.php/dav/&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;/.well-known/caldav&lt;/span&gt;  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kn"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;301&lt;/span&gt; &lt;span class="n"&gt;/remote.php/dav/&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/.well-known/acme-challenge&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kn"&gt;try_files&lt;/span&gt; &lt;span class="nv"&gt;$uri&lt;/span&gt; &lt;span class="nv"&gt;$uri&lt;/span&gt;&lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="kn"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;301&lt;/span&gt; &lt;span class="n"&gt;/index.php&lt;/span&gt;&lt;span class="nv"&gt;$request_uri&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="p"&gt;~&lt;/span&gt; &lt;span class="sr"&gt;^/(?:build|tests|config|lib|3rdparty|templates|data)(?:$|/)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kn"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="p"&gt;~&lt;/span&gt; &lt;span class="sr"&gt;^/(?:\.|autotest|occ|issue|indie|db_|console)&lt;/span&gt;               &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kn"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="p"&gt;~&lt;/span&gt; &lt;span class="sr"&gt;\.php(?:$|/)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kn"&gt;rewrite&lt;/span&gt; &lt;span class="s"&gt;^/(?!index|remote|public|cron|core&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;/ajax\/update|status|ocs\/v[12]|updater\/.+|ocs-provider\/.+|.+\/richdocumentscode&lt;/span&gt;&lt;span class="s"&gt;(_arm64)?&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;/proxy&lt;/span&gt;&lt;span class="s"&gt;)&lt;/span&gt; &lt;span class="n"&gt;/index.php&lt;/span&gt;&lt;span class="nv"&gt;$request_uri&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;fastcgi_split_path_info&lt;/span&gt; &lt;span class="s"&gt;^(.+?&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;.php)(/.*)&lt;/span&gt;$&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;$path_info&lt;/span&gt; &lt;span class="nv"&gt;$fastcgi_path_info&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;try_files&lt;/span&gt; &lt;span class="nv"&gt;$fastcgi_script_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;include&lt;/span&gt; &lt;span class="s"&gt;fastcgi_params&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;fastcgi_param&lt;/span&gt; &lt;span class="s"&gt;SCRIPT_FILENAME&lt;/span&gt; &lt;span class="nv"&gt;$document_root$fastcgi_script_name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;fastcgi_param&lt;/span&gt; &lt;span class="s"&gt;PATH_INFO&lt;/span&gt; &lt;span class="nv"&gt;$path_info&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;fastcgi_param&lt;/span&gt; &lt;span class="s"&gt;HTTPS&lt;/span&gt; &lt;span class="no"&gt;off&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;fastcgi_param&lt;/span&gt; &lt;span class="s"&gt;modHeadersAvailable&lt;/span&gt; &lt;span class="s"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;fastcgi_param&lt;/span&gt; &lt;span class="s"&gt;front_controller_active&lt;/span&gt; &lt;span class="s"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;fastcgi_pass&lt;/span&gt; &lt;span class="s"&gt;php-handler&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;fastcgi_intercept_errors&lt;/span&gt; &lt;span class="no"&gt;on&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;fastcgi_request_buffering&lt;/span&gt; &lt;span class="no"&gt;off&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;fastcgi_max_temp_file_size&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;fastcgi_read_timeout&lt;/span&gt; &lt;span class="s"&gt;360s&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="p"&gt;~&lt;/span&gt; &lt;span class="sr"&gt;\.(?:css|js|mjs|svg|gif|png|jpg|ico|wasm|map)$&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kn"&gt;try_files&lt;/span&gt; &lt;span class="nv"&gt;$uri&lt;/span&gt; &lt;span class="n"&gt;/index.php&lt;/span&gt;&lt;span class="nv"&gt;$request_uri&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;add_header&lt;/span&gt; &lt;span class="s"&gt;Cache-Control&lt;/span&gt; &lt;span class="s"&gt;"public,&lt;/span&gt; &lt;span class="s"&gt;max-age=15778463"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;access_log&lt;/span&gt; &lt;span class="no"&gt;off&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/remote&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kn"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;301&lt;/span&gt; &lt;span class="n"&gt;/remote.php&lt;/span&gt;&lt;span class="nv"&gt;$request_uri&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kn"&gt;try_files&lt;/span&gt; &lt;span class="nv"&gt;$uri&lt;/span&gt; &lt;span class="nv"&gt;$uri&lt;/span&gt;&lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="n"&gt;/index.php&lt;/span&gt;&lt;span class="nv"&gt;$request_uri&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two things that bit people before you: one, add &lt;code&gt;mjs&lt;/code&gt; to &lt;code&gt;/etc/nginx/mime.types&lt;/code&gt; (&lt;code&gt;application/javascript js mjs;&lt;/code&gt;) or parts of the web UI silently break. Two, if you'd rather use Apache2 — fine, the zip ships with a working &lt;code&gt;.htaccess&lt;/code&gt;, just &lt;code&gt;a2enmod rewrite headers env dir mime&lt;/code&gt; and point a vhost at &lt;code&gt;/var/www/nextcloud&lt;/code&gt;. I still think nginx is worth it.&lt;/p&gt;

&lt;p&gt;Enable and light it up:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo ln&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; /etc/nginx/sites-available/nextcloud /etc/nginx/sites-enabled/
&lt;span class="nb"&gt;sudo rm&lt;/span&gt; /etc/nginx/sites-enabled/default
&lt;span class="nb"&gt;sudo &lt;/span&gt;nginx &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl reload nginx
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl restart php8.5-fpm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The actual install
&lt;/h3&gt;

&lt;p&gt;Skip the web wizard. The CLI installer is faster and scriptable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; www-data php /var/www/nextcloud/occ maintenance:install &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--database&lt;/span&gt; pgsql &lt;span class="nt"&gt;--database-host&lt;/span&gt; 127.0.0.1 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--database-name&lt;/span&gt; nextcloud &lt;span class="nt"&gt;--database-user&lt;/span&gt; nextcloud &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--database-pass&lt;/span&gt; &lt;span class="s1"&gt;'pick-a-real-password'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--admin-user&lt;/span&gt; admin &lt;span class="nt"&gt;--admin-pass&lt;/span&gt; &lt;span class="s1"&gt;'a-better-password-than-this'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--data-dir&lt;/span&gt; /mnt/usb/nextcloud-data
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(&lt;code&gt;--database mysql&lt;/code&gt; for the MariaDB crowd.)&lt;/p&gt;

&lt;p&gt;Then the post-install config everyone forgets:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; /var/www/nextcloud
&lt;span class="nb"&gt;sudo&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; www-data php occ config:system:set trusted_domains 1 &lt;span class="nt"&gt;--value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;cloud.example.com
&lt;span class="nb"&gt;sudo&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; www-data php occ config:system:set memcache.local &lt;span class="nt"&gt;--value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'\OC\Memcache\APCu'&lt;/span&gt;
&lt;span class="nb"&gt;sudo&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; www-data php occ config:system:set memcache.locking &lt;span class="nt"&gt;--value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'\OC\Memcache\Redis'&lt;/span&gt;
&lt;span class="nb"&gt;sudo&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; www-data php occ config:system:set redis host &lt;span class="nt"&gt;--value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;127.0.0.1
&lt;span class="nb"&gt;sudo&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; www-data php occ config:system:set redis port &lt;span class="nt"&gt;--value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;6379 &lt;span class="nt"&gt;--type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;integer
&lt;span class="nb"&gt;sudo&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; www-data php occ config:system:set default_phone_region &lt;span class="nt"&gt;--value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;IN
&lt;span class="nb"&gt;sudo&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; www-data php occ background:cron
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the cron job that actually runs background tasks (without this, thumbnails and cleanup silently never happen):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"*/5 * * * * php -f /var/www/nextcloud/cron.php"&lt;/span&gt; | &lt;span class="nb"&gt;sudo &lt;/span&gt;crontab &lt;span class="nt"&gt;-u&lt;/span&gt; www-data -
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sanity check: &lt;code&gt;curl http://&amp;lt;server-ip&amp;gt;/status.php&lt;/code&gt; should return JSON with &lt;code&gt;"installed":true&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 2: The Docker way (if you hate fun)
&lt;/h2&gt;

&lt;p&gt;If the above looked like a lot — fair. Here's the same thing as one &lt;code&gt;docker-compose.yml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres:16&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;db:/var/lib/postgresql/data&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_DB&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nextcloud&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_USER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nextcloud&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pick-a-real-password&lt;/span&gt;

  &lt;span class="na"&gt;redis&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis:alpine&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always&lt;/span&gt;

  &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nextcloud:apache&lt;/span&gt;    &lt;span class="c1"&gt;# or nextcloud:fpm + an nginx container&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;8080:80&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;nextcloud:/var/www/html&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/mnt/usb/nextcloud-data:/var/www/html/data&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_HOST&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;db&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_DB&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nextcloud&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_USER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nextcloud&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pick-a-real-password&lt;/span&gt;
      &lt;span class="na"&gt;REDIS_HOST&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis&lt;/span&gt;
      &lt;span class="na"&gt;NEXTCLOUD_TRUSTED_DOMAINS&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cloud.example.com&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;db&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;redis&lt;/span&gt;

&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;nextcloud&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;docker compose up -d&lt;/code&gt; and you're at the same place. Swap &lt;code&gt;postgres:16&lt;/code&gt; for &lt;code&gt;mariadb:11&lt;/code&gt; if you prefer. Honest take: Docker is easier to start, but the native install is easier to &lt;em&gt;understand&lt;/em&gt; when something breaks at 2 AM — and something always breaks at 2 AM. Pick your poison knowingly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 3: HTTPS without opening ports
&lt;/h2&gt;

&lt;p&gt;My ISP puts me behind NAT anyway, so port forwarding was never on the table. Cloudflare Tunnel solved it in about four commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wget https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-arm64.deb
&lt;span class="nb"&gt;sudo &lt;/span&gt;dpkg &lt;span class="nt"&gt;-i&lt;/span&gt; cloudflared-linux-arm64.deb
cloudflared tunnel login        &lt;span class="c"&gt;# gives you a URL, open it, authorize your domain&lt;/span&gt;
cloudflared tunnel create mycloud
cloudflared tunnel route dns mycloud cloud.example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;/etc/cloudflared/config.yml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;tunnel&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;tunnel-id-from-the-create-command&amp;gt;&lt;/span&gt;
&lt;span class="na"&gt;credentials-file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/etc/cloudflared/&amp;lt;tunnel-id&amp;gt;.json&lt;/span&gt;
&lt;span class="na"&gt;ingress&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;hostname&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cloud.example.com&lt;/span&gt;
    &lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http://localhost:80&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http_status:404&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;cloudflared service &lt;span class="nb"&gt;install
sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable&lt;/span&gt; &lt;span class="nt"&gt;--now&lt;/span&gt; cloudflared
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Valid HTTPS, real cert, zero open inbound ports. Then tell Nextcloud about its public identity:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; www-data php occ config:system:set overwriteprotocol &lt;span class="nt"&gt;--value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https
&lt;span class="nb"&gt;sudo&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; www-data php occ config:system:set overwritehost &lt;span class="nt"&gt;--value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;cloud.example.com
&lt;span class="nb"&gt;sudo&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; www-data php occ config:system:set overwrite.cli.url &lt;span class="nt"&gt;--value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://cloud.example.com
&lt;span class="nb"&gt;sudo&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; www-data php occ config:system:set trusted_proxies 0 &lt;span class="nt"&gt;--value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;127.0.0.1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That last one matters — without it, every visitor in your logs shows up as &lt;code&gt;127.0.0.1&lt;/code&gt; and your brute-force protection can't tell attackers apart.&lt;/p&gt;

&lt;p&gt;Since I own the domain anyway, I also themed the login page — logo, colors, the lot. This is what greets me now:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9rob9wzqcquj06zogslw.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9rob9wzqcquj06zogslw.webp" alt="Themed login page" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 4: Locking it down
&lt;/h2&gt;

&lt;p&gt;Since it's internet-facing, minimum viable paranoia:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Firewall: LAN only for SSH and HTTP. The world only enters via the tunnel.&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;ufw default deny incoming
&lt;span class="nb"&gt;sudo &lt;/span&gt;ufw default allow outgoing
&lt;span class="nb"&gt;sudo &lt;/span&gt;ufw allow from 192.168.1.0/24 to any port 22 proto tcp
&lt;span class="nb"&gt;sudo &lt;/span&gt;ufw allow from 192.168.1.0/24 to any port 80 proto tcp
&lt;span class="nb"&gt;sudo &lt;/span&gt;ufw &lt;span class="nt"&gt;--force&lt;/span&gt; &lt;span class="nb"&gt;enable&lt;/span&gt;

&lt;span class="c"&gt;# fail2ban for SSH and Nextcloud logins&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; fail2ban
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Nextcloud jail needs a custom filter because the log is JSON — &lt;code&gt;/etc/fail2ban/filter.d/nextcloud.conf&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[Definition]&lt;/span&gt;
&lt;span class="py"&gt;_groupsre&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;(?:(?:,?&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="s"&gt;*"&lt;/span&gt;&lt;span class="se"&gt;\w&lt;/span&gt;&lt;span class="s"&gt;+":(?:"(?:&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s"&gt;.|[^"])+"|&lt;/span&gt;&lt;span class="se"&gt;\w&lt;/span&gt;&lt;span class="s"&gt;+))*)&lt;/span&gt;
&lt;span class="py"&gt;failregex&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;^&lt;/span&gt;&lt;span class="se"&gt;\{&lt;/span&gt;&lt;span class="s"&gt;%(_groupsre)s,?&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="s"&gt;*"remoteAddr":"&amp;lt;HOST&amp;gt;"%(_groupsre)s,?&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="s"&gt;*"message":"Login failed:&lt;/span&gt;
&lt;span class="py"&gt;datepattern&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;,?&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="s"&gt;*"time"&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="s"&gt;*:&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="s"&gt;*"%%Y-%%m-%%dT%%H:%%M:%%S%%z"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;/etc/fail2ban/jail.local&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[sshd]&lt;/span&gt;
&lt;span class="py"&gt;enabled&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;true&lt;/span&gt;
&lt;span class="py"&gt;maxretry&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;4&lt;/span&gt;
&lt;span class="py"&gt;bantime&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;1h&lt;/span&gt;

&lt;span class="nn"&gt;[nextcloud]&lt;/span&gt;
&lt;span class="py"&gt;enabled&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;true&lt;/span&gt;
&lt;span class="py"&gt;port&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;80,443&lt;/span&gt;
&lt;span class="py"&gt;filter&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;nextcloud&lt;/span&gt;
&lt;span class="py"&gt;logpath&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;/mnt/usb/nextcloud-data/nextcloud.log&lt;/span&gt;
&lt;span class="py"&gt;maxretry&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;5&lt;/span&gt;
&lt;span class="py"&gt;bantime&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;1h&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Plus inside Nextcloud itself: enable the &lt;strong&gt;password_policy&lt;/strong&gt; app (12+ chars, block breached passwords), turn on &lt;strong&gt;TOTP 2FA&lt;/strong&gt; for your account, and enable the &lt;strong&gt;Suspicious Login&lt;/strong&gt; app. Then run &lt;code&gt;sudo -u www-data php occ setupchecks&lt;/code&gt; — it grades your homework. Keep going until it stops complaining.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 5: The phone part (the actual point of all this)
&lt;/h2&gt;

&lt;p&gt;Install the Nextcloud Android app, type your domain, log in, and enable &lt;strong&gt;Auto upload&lt;/strong&gt; for the camera folder. Then install the &lt;strong&gt;Memories&lt;/strong&gt; app on the server (&lt;code&gt;occ app:install memories&lt;/code&gt;, or from the web UI) — that's what gives you the Google-Photos-style timeline, map view, and "on this day" nostalgia hits. I also added Calendar, Contacts, Notes and Tasks; with DAVx⁵ from F-Droid, my phone's native calendar and contacts now sync to my closet instead of Mountain View.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it looks like
&lt;/h2&gt;

&lt;p&gt;All of these are from the actual box. I made a throwaway account with stock photos for the screenshots — my real library stays private, which is rather the point of the whole exercise.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8t21lrv4qbys7a0yzku8.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8t21lrv4qbys7a0yzku8.webp" alt="Dashboard — dark theme, recommended files" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F304urupuzuamlf789clp.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F304urupuzuamlf789clp.webp" alt="Files — folders and uploads" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgxsyiimxagcbqntbdmvc.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgxsyiimxagcbqntbdmvc.webp" alt="Photos app — timeline grid" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F05zjjwmp9b22zh5xk6nq.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F05zjjwmp9b22zh5xk6nq.webp" alt="Memories — the Google Photos-style timeline" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Was it worth it?
&lt;/h2&gt;

&lt;p&gt;Honest answer: as a file-syncing appliance, Google is still smoother. Their search is magic, their apps are polished, and nobody calls you when &lt;em&gt;their&lt;/em&gt; server breaks at 2 AM.&lt;/p&gt;

&lt;p&gt;But my photos live on a drive I can unplug and hold in my hand. Nobody's training anything on them. Nobody's holding them for ransom at ₹1,300 a year. And when I pull a photo from three years ago on my phone, it's served out of my closet by a board that draws less power than the LED on my TV.&lt;/p&gt;

&lt;p&gt;Some things you do because they're practical. Some you do because they're &lt;em&gt;yours&lt;/em&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Full configs from this build are in the post — everything ran on Ubuntu (ARM64) with Nextcloud 35, nginx 1.28, PostgreSQL 18 and Redis 8. If you build this and something explodes, the comments are open.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Appendix: the full setup script
&lt;/h2&gt;

&lt;p&gt;Everything in this post, automated. Also available as a &lt;a href="https://clinerds.com/covers/nextcloud/setup-nextcloud.sh" rel="noopener noreferrer"&gt;downloadable file&lt;/a&gt; — read it first, then &lt;code&gt;chmod +x setup-nextcloud.sh &amp;amp;&amp;amp; ./setup-nextcloud.sh&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# setup-nextcloud.sh — self-hosted Nextcloud on bare metal, no Docker, no snap.&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# i run this on the little ARM board in my closet, but it works on any&lt;/span&gt;
&lt;span class="c"&gt;# ubuntu/debian-ish x86 box too. it asks you a few questions, then sets up:&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;#   nginx or apache  +  php-fpm  +  postgres or mariadb  +  redis&lt;/span&gt;
&lt;span class="c"&gt;#   nextcloud itself, cron, caching, and (optionally) a cloudflare tunnel&lt;/span&gt;
&lt;span class="c"&gt;#   so you get real https without opening ports on your router.&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# read it before you run it. that's just good hygiene.&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;#   chmod +x setup-nextcloud.sh &amp;amp;&amp;amp; ./setup-nextcloud.sh&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;

&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-euo&lt;/span&gt; pipefail

&lt;span class="c"&gt;# -- pretty output ------------------------------------------------------------&lt;/span&gt;
&lt;span class="nv"&gt;c_cyan&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;$'&lt;/span&gt;&lt;span class="se"&gt;\e&lt;/span&gt;&lt;span class="s1"&gt;[36m'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;c_green&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;$'&lt;/span&gt;&lt;span class="se"&gt;\e&lt;/span&gt;&lt;span class="s1"&gt;[32m'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;c_yellow&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;$'&lt;/span&gt;&lt;span class="se"&gt;\e&lt;/span&gt;&lt;span class="s1"&gt;[33m'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;c_red&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;$'&lt;/span&gt;&lt;span class="se"&gt;\e&lt;/span&gt;&lt;span class="s1"&gt;[31m'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;c_off&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;$'&lt;/span&gt;&lt;span class="se"&gt;\e&lt;/span&gt;&lt;span class="s1"&gt;[0m'&lt;/span&gt;
say&lt;span class="o"&gt;()&lt;/span&gt;  &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;c_cyan&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;==&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;c_off&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$*&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
ok&lt;span class="o"&gt;()&lt;/span&gt;   &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;c_green&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;  ok&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;c_off&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$*&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
warn&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;c_yellow&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;  !!&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;c_off&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$*&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
die&lt;span class="o"&gt;()&lt;/span&gt;  &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;c_red&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;  xx&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;c_off&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$*&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&amp;amp;2&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;1&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;# -- sanity checks --------------------------------------------------------------&lt;/span&gt;
&lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nv"&gt;$EUID&lt;/span&gt; &lt;span class="nt"&gt;-eq&lt;/span&gt; 0 &lt;span class="o"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; die &lt;span class="s2"&gt;"don't run me as root directly. run as your normal user; i'll sudo when needed."&lt;/span&gt;
&lt;span class="nb"&gt;command&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; apt-get &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null &lt;span class="o"&gt;||&lt;/span&gt; die &lt;span class="s2"&gt;"this script speaks apt. debian/ubuntu only, sorry."&lt;/span&gt;
&lt;span class="nb"&gt;sudo&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; die &lt;span class="s2"&gt;"need sudo."&lt;/span&gt;

&lt;span class="c"&gt;# fresh-booted ubuntu boxes love holding the dpkg lock hostage for a bit&lt;/span&gt;
say &lt;span class="s2"&gt;"waiting for any background apt runs to finish"&lt;/span&gt;
&lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;fuser /var/lib/dpkg/lock-frontend &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null 2&amp;gt;&amp;amp;1&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do &lt;/span&gt;&lt;span class="nb"&gt;sleep &lt;/span&gt;5&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;done&lt;/span&gt;

&lt;span class="c"&gt;# -- questions ------------------------------------------------------------------&lt;/span&gt;
&lt;span class="nb"&gt;echo
&lt;/span&gt;say &lt;span class="s2"&gt;"a few questions before we start"&lt;/span&gt;

&lt;span class="nb"&gt;read&lt;/span&gt; &lt;span class="nt"&gt;-rp&lt;/span&gt; &lt;span class="s2"&gt;"  public domain for the cloud (e.g. cloud.example.com, blank = lan only): "&lt;/span&gt; NC_DOMAIN
&lt;span class="nv"&gt;NC_DOMAIN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;NC_DOMAIN&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;hostname&lt;/span&gt; &lt;span class="nt"&gt;-I&lt;/span&gt; | &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'{print $1}'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;

&lt;span class="nb"&gt;read&lt;/span&gt; &lt;span class="nt"&gt;-rp&lt;/span&gt; &lt;span class="s2"&gt;"  web server [nginx/apache] (default nginx): "&lt;/span&gt; WEB
&lt;span class="nv"&gt;WEB&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;WEB&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="nv"&gt;nginx&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;

&lt;span class="nb"&gt;read&lt;/span&gt; &lt;span class="nt"&gt;-rp&lt;/span&gt; &lt;span class="s2"&gt;"  database [postgres/mariadb] (default postgres): "&lt;/span&gt; DB
&lt;span class="nv"&gt;DB&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;DB&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="nv"&gt;postgres&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;

&lt;span class="nb"&gt;read&lt;/span&gt; &lt;span class="nt"&gt;-rp&lt;/span&gt; &lt;span class="s2"&gt;"  data directory (default /var/nextcloud-data): "&lt;/span&gt; DATA_DIR
&lt;span class="nv"&gt;DATA_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;DATA_DIR&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="p"&gt;/var/nextcloud-data&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;

&lt;span class="nb"&gt;read&lt;/span&gt; &lt;span class="nt"&gt;-rp&lt;/span&gt; &lt;span class="s2"&gt;"  admin username (default admin): "&lt;/span&gt; ADMIN_USER
&lt;span class="nv"&gt;ADMIN_USER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;ADMIN_USER&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="nv"&gt;admin&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;
&lt;span class="nb"&gt;read&lt;/span&gt; &lt;span class="nt"&gt;-rsp&lt;/span&gt; &lt;span class="s2"&gt;"  admin password: "&lt;/span&gt; ADMIN_PASS&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt;
&lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ADMIN_PASS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; die &lt;span class="s2"&gt;"empty password? nice try."&lt;/span&gt;

&lt;span class="nv"&gt;DB_PASS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;openssl rand &lt;span class="nt"&gt;-base64&lt;/span&gt; 24 | &lt;span class="nb"&gt;tr&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'/+='&lt;/span&gt; | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; 24&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# warn gently if the data dir is about to live on the root sd card&lt;/span&gt;
&lt;span class="nv"&gt;ROOT_SRC&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;findmnt &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; SOURCE /&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;DATA_SRC&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;findmnt &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; SOURCE &lt;span class="nt"&gt;-T&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DATA_DIR&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; 2&amp;gt;/dev/null &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ROOT_SRC&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DATA_SRC&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ROOT_SRC&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ROOT_SRC&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt;mmcblk&lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;warn &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DATA_DIR&lt;/span&gt;&lt;span class="s2"&gt; will sit on the SD card (&lt;/span&gt;&lt;span class="nv"&gt;$ROOT_SRC&lt;/span&gt;&lt;span class="s2"&gt;)."&lt;/span&gt;
    warn &lt;span class="s2"&gt;"sd cards die young under database + thumbnail workloads."&lt;/span&gt;
    &lt;span class="nb"&gt;read&lt;/span&gt; &lt;span class="nt"&gt;-rp&lt;/span&gt; &lt;span class="s2"&gt;"  continue anyway? [y/N]: "&lt;/span&gt; YOLO
    &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;YOLO&lt;/span&gt;&lt;span class="p"&gt;,,&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"y"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; die &lt;span class="s2"&gt;"mount a usb drive first, point the data dir at it, rerun me."&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;

&lt;span class="c"&gt;# -- packages -------------------------------------------------------------------&lt;/span&gt;
say &lt;span class="s2"&gt;"installing packages"&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get update &lt;span class="nt"&gt;-qq&lt;/span&gt;

&lt;span class="nv"&gt;PHP_PKGS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"php-fpm php-gd php-curl php-zip php-xml php-mbstring php-intl php-bcmath php-gmp php-imagick php-redis php-apcu"&lt;/span&gt;
&lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DB&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; postgres &lt;span class="o"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nv"&gt;PHP_PKGS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PHP_PKGS&lt;/span&gt;&lt;span class="s2"&gt; php-pgsql"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nv"&gt;PHP_PKGS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PHP_PKGS&lt;/span&gt;&lt;span class="s2"&gt; php-mysql"&lt;/span&gt;

&lt;span class="nb"&gt;sudo &lt;/span&gt;&lt;span class="nv"&gt;DEBIAN_FRONTEND&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;noninteractive apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="nt"&gt;-qq&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    redis-server unzip curl openssl ffmpeg &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nv"&gt;$PHP_PKGS&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="si"&gt;$(&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$WEB&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; nginx &lt;span class="o"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo &lt;/span&gt;nginx &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"apache2 libapache2-mod-php"&lt;/span&gt; &lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="si"&gt;$(&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DB&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; postgres &lt;span class="o"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo &lt;/span&gt;postgresql &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;echo &lt;/span&gt;mariadb-server &lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="nv"&gt;PHP_VER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;php &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'echo PHP_MAJOR_VERSION.".".PHP_MINOR_VERSION;'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;PHP_SOCK&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/run/php/php&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PHP_VER&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-fpm.sock"&lt;/span&gt;
ok &lt;span class="s2"&gt;"php &lt;/span&gt;&lt;span class="nv"&gt;$PHP_VER&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="nv"&gt;$WEB&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="nv"&gt;$DB&lt;/span&gt;&lt;span class="s2"&gt;, redis — all in"&lt;/span&gt;

&lt;span class="c"&gt;# -- database -------------------------------------------------------------------&lt;/span&gt;
say &lt;span class="s2"&gt;"setting up the database"&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DB&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; postgres &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;sudo&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; postgres psql &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="nv"&gt;ON_ERROR_STOP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;SQL&lt;/span&gt;&lt;span class="sh"&gt;
CREATE USER nextcloud WITH PASSWORD '&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;DB_PASS&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;';
CREATE DATABASE nextcloud OWNER nextcloud TEMPLATE template0 ENCODING 'UTF8';
&lt;/span&gt;&lt;span class="no"&gt;SQL
&lt;/span&gt;&lt;span class="k"&gt;else
    &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;mariadb &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;SQL&lt;/span&gt;&lt;span class="sh"&gt;
CREATE DATABASE nextcloud CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
CREATE USER 'nextcloud'@'localhost' IDENTIFIED BY '&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;DB_PASS&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;';
GRANT ALL PRIVILEGES ON nextcloud.* TO 'nextcloud'@'localhost';
FLUSH PRIVILEGES;
&lt;/span&gt;&lt;span class="no"&gt;SQL
&lt;/span&gt;&lt;span class="k"&gt;fi
&lt;/span&gt;ok &lt;span class="s2"&gt;"database ready"&lt;/span&gt;

&lt;span class="c"&gt;# -- nextcloud ------------------------------------------------------------------&lt;/span&gt;
say &lt;span class="s2"&gt;"fetching nextcloud"&lt;/span&gt;
wget &lt;span class="nt"&gt;-q&lt;/span&gt; https://download.nextcloud.com/server/releases/latest.zip &lt;span class="nt"&gt;-O&lt;/span&gt; /tmp/nextcloud.zip
&lt;span class="nb"&gt;sudo &lt;/span&gt;unzip &lt;span class="nt"&gt;-q&lt;/span&gt; /tmp/nextcloud.zip &lt;span class="nt"&gt;-d&lt;/span&gt; /var/www/
&lt;span class="nb"&gt;sudo mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DATA_DIR&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;sudo chown&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; www-data:www-data /var/www/nextcloud &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DATA_DIR&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;sudo chmod &lt;/span&gt;750 &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DATA_DIR&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
ok &lt;span class="s2"&gt;"nextcloud unpacked to /var/www/nextcloud"&lt;/span&gt;

&lt;span class="c"&gt;# -- php tuning -----------------------------------------------------------------&lt;/span&gt;
say &lt;span class="s2"&gt;"tuning php"&lt;/span&gt;
&lt;span class="nb"&gt;sudo tee&lt;/span&gt; /etc/php/&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PHP_VER&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;/fpm/conf.d/99-nextcloud.ini &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;INI&lt;/span&gt;&lt;span class="sh"&gt;'
memory_limit = 512M
upload_max_filesize = 10G
post_max_size = 10G
max_execution_time = 360
output_buffering = Off
opcache.enable = 1
opcache.interned_strings_buffer = 16
opcache.max_accelerated_files = 10000
opcache.memory_consumption = 256
opcache.save_comments = 1
opcache.revalidate_freq = 60
apc.enable_cli = 1
&lt;/span&gt;&lt;span class="no"&gt;INI
&lt;/span&gt;&lt;span class="c"&gt;# cli needs the same settings or occ behaves differently from the web app.&lt;/span&gt;
&lt;span class="nb"&gt;sudo cp&lt;/span&gt; /etc/php/&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PHP_VER&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;/fpm/conf.d/99-nextcloud.ini /etc/php/&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PHP_VER&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;/cli/conf.d/99-nextcloud.ini

&lt;span class="c"&gt;# -- web server -----------------------------------------------------------------&lt;/span&gt;
say &lt;span class="s2"&gt;"configuring &lt;/span&gt;&lt;span class="nv"&gt;$WEB&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$WEB&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; nginx &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
    &lt;span class="c"&gt;# .mjs missing from mime.types breaks parts of the ui. ask me how i know.&lt;/span&gt;
    &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-q&lt;/span&gt; &lt;span class="s1"&gt;'mjs'&lt;/span&gt; /etc/nginx/mime.types &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
        &lt;span class="nb"&gt;sudo sed&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s1"&gt;'s|application/javascript\s\+js;|application/javascript                           js mjs;|'&lt;/span&gt; /etc/nginx/mime.types

    &lt;span class="nb"&gt;sudo tee&lt;/span&gt; /etc/nginx/sites-available/nextcloud &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;NGINX&lt;/span&gt;&lt;span class="sh"&gt;
upstream php-handler {
    server unix:&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PHP_SOCK&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;;
}

server {
    listen 80;
    listen [::]:80;
    server_name &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;NC_DOMAIN&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;;

    root /var/www/nextcloud;
    server_tokens off;
    client_max_body_size 10G;
    client_body_timeout 360s;

    add_header Strict-Transport-Security "max-age=15552000; includeSubDomains" always;
    add_header X-Content-Type-Options     "nosniff"      always;
    add_header X-Frame-Options            "SAMEORIGIN"   always;
    add_header Referrer-Policy            "no-referrer"  always;

    fastcgi_hide_header X-Powered-By;
    index index.php index.html /index.php&lt;/span&gt;&lt;span class="se"&gt;\$&lt;/span&gt;&lt;span class="sh"&gt;request_uri;

    location = /robots.txt { allow all; log_not_found off; access_log off; }

    location ^~ /.well-known {
        location = /.well-known/carddav { return 301 /remote.php/dav/; }
        location = /.well-known/caldav  { return 301 /remote.php/dav/; }
        location /.well-known/acme-challenge { try_files &lt;/span&gt;&lt;span class="se"&gt;\$&lt;/span&gt;&lt;span class="sh"&gt;uri &lt;/span&gt;&lt;span class="se"&gt;\$&lt;/span&gt;&lt;span class="sh"&gt;uri/ =404; }
        return 301 /index.php&lt;/span&gt;&lt;span class="se"&gt;\$&lt;/span&gt;&lt;span class="sh"&gt;request_uri;
    }

    location ~ ^/(?:build|tests|config|lib|3rdparty|templates|data)(?:&lt;/span&gt;&lt;span class="se"&gt;\$&lt;/span&gt;&lt;span class="sh"&gt;|/) { return 404; }
    location ~ ^/(?:&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="sh"&gt;|autotest|occ|issue|indie|db_|console)                { return 404; }

    location ~ &lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="sh"&gt;php(?:&lt;/span&gt;&lt;span class="se"&gt;\$&lt;/span&gt;&lt;span class="sh"&gt;|/) {
        rewrite ^/(?!index|remote|public|cron|core&lt;/span&gt;&lt;span class="se"&gt;\/&lt;/span&gt;&lt;span class="sh"&gt;ajax&lt;/span&gt;&lt;span class="se"&gt;\/&lt;/span&gt;&lt;span class="sh"&gt;update|status|ocs&lt;/span&gt;&lt;span class="se"&gt;\/&lt;/span&gt;&lt;span class="sh"&gt;v[12]|updater&lt;/span&gt;&lt;span class="se"&gt;\/&lt;/span&gt;&lt;span class="sh"&gt;.+|ocs-provider&lt;/span&gt;&lt;span class="se"&gt;\/&lt;/span&gt;&lt;span class="sh"&gt;.+|.+&lt;/span&gt;&lt;span class="se"&gt;\/&lt;/span&gt;&lt;span class="sh"&gt;richdocumentscode(_arm64)?&lt;/span&gt;&lt;span class="se"&gt;\/&lt;/span&gt;&lt;span class="sh"&gt;proxy) /index.php&lt;/span&gt;&lt;span class="se"&gt;\$&lt;/span&gt;&lt;span class="sh"&gt;request_uri;
        fastcgi_split_path_info ^(.+?&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="sh"&gt;php)(/.*)&lt;/span&gt;&lt;span class="nv"&gt;$;&lt;/span&gt;&lt;span class="sh"&gt;
        set &lt;/span&gt;&lt;span class="se"&gt;\$&lt;/span&gt;&lt;span class="sh"&gt;path_info &lt;/span&gt;&lt;span class="se"&gt;\$&lt;/span&gt;&lt;span class="sh"&gt;fastcgi_path_info;
        try_files &lt;/span&gt;&lt;span class="se"&gt;\$&lt;/span&gt;&lt;span class="sh"&gt;fastcgi_script_name =404;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME &lt;/span&gt;&lt;span class="se"&gt;\$&lt;/span&gt;&lt;span class="sh"&gt;document_root&lt;/span&gt;&lt;span class="se"&gt;\$&lt;/span&gt;&lt;span class="sh"&gt;fastcgi_script_name;
        fastcgi_param PATH_INFO &lt;/span&gt;&lt;span class="se"&gt;\$&lt;/span&gt;&lt;span class="sh"&gt;path_info;
        fastcgi_param HTTPS off;
        fastcgi_param modHeadersAvailable true;
        fastcgi_param front_controller_active true;
        fastcgi_pass php-handler;
        fastcgi_intercept_errors on;
        fastcgi_request_buffering off;
        fastcgi_max_temp_file_size 0;
        fastcgi_read_timeout 360s;
    }

    location ~ &lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="sh"&gt;(?:css|js|mjs|svg|gif|png|jpg|ico|wasm|map)&lt;/span&gt;&lt;span class="se"&gt;\$&lt;/span&gt;&lt;span class="sh"&gt; {
        try_files &lt;/span&gt;&lt;span class="se"&gt;\$&lt;/span&gt;&lt;span class="sh"&gt;uri /index.php&lt;/span&gt;&lt;span class="se"&gt;\$&lt;/span&gt;&lt;span class="sh"&gt;request_uri;
        add_header Cache-Control "public, max-age=15778463";
        access_log off;
    }

    location /remote { return 301 /remote.php&lt;/span&gt;&lt;span class="se"&gt;\$&lt;/span&gt;&lt;span class="sh"&gt;request_uri; }

    location / {
        try_files &lt;/span&gt;&lt;span class="se"&gt;\$&lt;/span&gt;&lt;span class="sh"&gt;uri &lt;/span&gt;&lt;span class="se"&gt;\$&lt;/span&gt;&lt;span class="sh"&gt;uri/ /index.php&lt;/span&gt;&lt;span class="se"&gt;\$&lt;/span&gt;&lt;span class="sh"&gt;request_uri;
    }
}
&lt;/span&gt;&lt;span class="no"&gt;NGINX
&lt;/span&gt;    &lt;span class="nb"&gt;sudo ln&lt;/span&gt; &lt;span class="nt"&gt;-sf&lt;/span&gt; /etc/nginx/sites-available/nextcloud /etc/nginx/sites-enabled/nextcloud
    &lt;span class="nb"&gt;sudo rm&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; /etc/nginx/sites-enabled/default
    &lt;span class="nb"&gt;sudo &lt;/span&gt;nginx &lt;span class="nt"&gt;-t&lt;/span&gt;
    &lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl reload nginx
&lt;span class="k"&gt;else
    &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;a2enmod rewrite headers &lt;span class="nb"&gt;env dir &lt;/span&gt;mime ssl &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null
    &lt;span class="nb"&gt;sudo tee&lt;/span&gt; /etc/apache2/sites-available/nextcloud.conf &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;APACHE&lt;/span&gt;&lt;span class="sh"&gt;
&amp;lt;VirtualHost *:80&amp;gt;
    ServerName &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;NC_DOMAIN&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;
    DocumentRoot /var/www/nextcloud
    &amp;lt;Directory /var/www/nextcloud&amp;gt;
        Require all granted
        AllowOverride All
        Options FollowSymLinks MultiViews
    &amp;lt;/Directory&amp;gt;
&amp;lt;/VirtualHost&amp;gt;
&lt;/span&gt;&lt;span class="no"&gt;APACHE
&lt;/span&gt;    &lt;span class="nb"&gt;sudo &lt;/span&gt;a2ensite nextcloud &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null
    &lt;span class="nb"&gt;sudo &lt;/span&gt;a2dissite 000-default &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null 2&amp;gt;&amp;amp;1 &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true
    sudo &lt;/span&gt;systemctl reload apache2
&lt;span class="k"&gt;fi
&lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl restart &lt;span class="s2"&gt;"php&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PHP_VER&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-fpm"&lt;/span&gt;
ok &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$WEB&lt;/span&gt;&lt;span class="s2"&gt; is serving"&lt;/span&gt;

&lt;span class="c"&gt;# -- install --------------------------------------------------------------------&lt;/span&gt;
say &lt;span class="s2"&gt;"running the nextcloud installer"&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DB&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; postgres &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nv"&gt;DBARGS&lt;/span&gt;&lt;span class="o"&gt;=(&lt;/span&gt;&lt;span class="nt"&gt;--database&lt;/span&gt; pgsql &lt;span class="nt"&gt;--database-host&lt;/span&gt; 127.0.0.1&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;else
    &lt;/span&gt;&lt;span class="nv"&gt;DBARGS&lt;/span&gt;&lt;span class="o"&gt;=(&lt;/span&gt;&lt;span class="nt"&gt;--database&lt;/span&gt; mysql&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;fi

&lt;/span&gt;&lt;span class="nb"&gt;sudo&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; www-data php /var/www/nextcloud/occ maintenance:install &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;DBARGS&lt;/span&gt;&lt;span class="p"&gt;[@]&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--database-name&lt;/span&gt; nextcloud &lt;span class="nt"&gt;--database-user&lt;/span&gt; nextcloud &lt;span class="nt"&gt;--database-pass&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DB_PASS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--admin-user&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ADMIN_USER&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--admin-pass&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ADMIN_PASS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--data-dir&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DATA_DIR&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
ok &lt;span class="s2"&gt;"installed"&lt;/span&gt;

&lt;span class="c"&gt;# -- config ---------------------------------------------------------------------&lt;/span&gt;
say &lt;span class="s2"&gt;"wiring up cache, cron, trusted domains"&lt;/span&gt;
occ&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;sudo&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; www-data php /var/www/nextcloud/occ &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$@&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;

occ config:system:set trusted_domains 1 &lt;span class="nt"&gt;--value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$NC_DOMAIN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
occ config:system:set memcache.local &lt;span class="nt"&gt;--value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'\OC\Memcache\APCu'&lt;/span&gt;
occ config:system:set memcache.locking &lt;span class="nt"&gt;--value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'\OC\Memcache\Redis'&lt;/span&gt;
occ config:system:set redis host &lt;span class="nt"&gt;--value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;127.0.0.1
occ config:system:set redis port &lt;span class="nt"&gt;--value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;6379 &lt;span class="nt"&gt;--type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;integer
occ config:system:set default_phone_region &lt;span class="nt"&gt;--value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;IN
occ background:cron
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"*/5 * * * * php -f /var/www/nextcloud/cron.php"&lt;/span&gt; | &lt;span class="nb"&gt;sudo &lt;/span&gt;crontab &lt;span class="nt"&gt;-u&lt;/span&gt; www-data -

&lt;span class="c"&gt;# -- optional: cloudflare tunnel --------------------------------------------------&lt;/span&gt;
&lt;span class="nb"&gt;echo
read&lt;/span&gt; &lt;span class="nt"&gt;-rp&lt;/span&gt; &lt;span class="s2"&gt;"  set up a cloudflare tunnel for https + remote access? [y/N]: "&lt;/span&gt; TUNNEL
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TUNNEL&lt;/span&gt;&lt;span class="p"&gt;,,&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"y"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;say &lt;span class="s2"&gt;"installing cloudflared"&lt;/span&gt;
    &lt;span class="nv"&gt;ARCH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;dpkg &lt;span class="nt"&gt;--print-architecture&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
    wget &lt;span class="nt"&gt;-q&lt;/span&gt; &lt;span class="s2"&gt;"https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;ARCH&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.deb"&lt;/span&gt; &lt;span class="nt"&gt;-O&lt;/span&gt; /tmp/cloudflared.deb
    &lt;span class="nb"&gt;sudo &lt;/span&gt;dpkg &lt;span class="nt"&gt;-i&lt;/span&gt; /tmp/cloudflared.deb

    warn &lt;span class="s2"&gt;"this next step prints a url — open it in your browser and authorize the domain."&lt;/span&gt;
    &lt;span class="nb"&gt;read&lt;/span&gt; &lt;span class="nt"&gt;-rp&lt;/span&gt; &lt;span class="s2"&gt;"  press enter when ready"&lt;/span&gt; _
    cloudflared tunnel login
    cloudflared tunnel create nextcloud
    cloudflared tunnel route dns nextcloud &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$NC_DOMAIN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

    &lt;span class="nv"&gt;TUNNEL_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;cloudflared tunnel list 2&amp;gt;/dev/null | &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'/nextcloud/ {print $1}'&lt;/span&gt; | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-1&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;sudo mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /etc/cloudflared
    &lt;span class="nb"&gt;sudo cp&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/.cloudflared/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TUNNEL_ID&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.json"&lt;/span&gt; /etc/cloudflared/
    &lt;span class="nb"&gt;sudo chmod &lt;/span&gt;600 /etc/cloudflared/&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TUNNEL_ID&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;.json
    &lt;span class="nb"&gt;sudo tee&lt;/span&gt; /etc/cloudflared/config.yml &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;YAML&lt;/span&gt;&lt;span class="sh"&gt;
tunnel: &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TUNNEL_ID&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;
credentials-file: /etc/cloudflared/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TUNNEL_ID&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;.json
ingress:
  - hostname: &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;NC_DOMAIN&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;
    service: http://localhost:80
  - service: http_status:404
&lt;/span&gt;&lt;span class="no"&gt;YAML
&lt;/span&gt;    &lt;span class="nb"&gt;sudo &lt;/span&gt;cloudflared service &lt;span class="nb"&gt;install
    sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable&lt;/span&gt; &lt;span class="nt"&gt;--now&lt;/span&gt; cloudflared

    occ config:system:set overwriteprotocol &lt;span class="nt"&gt;--value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https
    occ config:system:set overwritehost &lt;span class="nt"&gt;--value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$NC_DOMAIN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    occ config:system:set overwrite.cli.url &lt;span class="nt"&gt;--value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"https://&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;NC_DOMAIN&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="c"&gt;# without this every visitor looks like 127.0.0.1 and brute-force protection goes blind&lt;/span&gt;
    occ config:system:set trusted_proxies 0 &lt;span class="nt"&gt;--value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;127.0.0.1
    ok &lt;span class="s2"&gt;"tunnel live — https://&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;NC_DOMAIN&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;

&lt;span class="c"&gt;# -- optional: hardening ----------------------------------------------------------&lt;/span&gt;
&lt;span class="nb"&gt;echo
read&lt;/span&gt; &lt;span class="nt"&gt;-rp&lt;/span&gt; &lt;span class="s2"&gt;"  add firewall + fail2ban hardening? [Y/n]: "&lt;/span&gt; HARDEN
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;HARDEN&lt;/span&gt;&lt;span class="p"&gt;,,&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s2"&gt;"n"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;say &lt;span class="s2"&gt;"hardening"&lt;/span&gt;
    &lt;span class="nb"&gt;sudo &lt;/span&gt;&lt;span class="nv"&gt;DEBIAN_FRONTEND&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;noninteractive apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="nt"&gt;-qq&lt;/span&gt; ufw fail2ban

    &lt;span class="nv"&gt;MY_NET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;ip &lt;span class="nt"&gt;-4&lt;/span&gt; route show default | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-1&lt;/span&gt; | &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'{print $3}'&lt;/span&gt; | &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="s1"&gt;'s/\.[0-9]*$/.0\/24/'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;sudo &lt;/span&gt;ufw default deny incoming
    &lt;span class="nb"&gt;sudo &lt;/span&gt;ufw default allow outgoing
    &lt;span class="nb"&gt;sudo &lt;/span&gt;ufw allow from &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$MY_NET&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; to any port 22 proto tcp
    &lt;span class="nb"&gt;sudo &lt;/span&gt;ufw allow from &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$MY_NET&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; to any port 80 proto tcp
    &lt;span class="nb"&gt;sudo &lt;/span&gt;ufw &lt;span class="nt"&gt;--force&lt;/span&gt; &lt;span class="nb"&gt;enable&lt;/span&gt;

    &lt;span class="c"&gt;# nextcloud logs failed logins as json; stock fail2ban has no filter for that&lt;/span&gt;
    &lt;span class="nb"&gt;sudo tee&lt;/span&gt; /etc/fail2ban/filter.d/nextcloud.conf &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;F2B&lt;/span&gt;&lt;span class="sh"&gt;'
[Definition]
_groupsre = (?:(?:,?&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sh"&gt;*"&lt;/span&gt;&lt;span class="se"&gt;\w&lt;/span&gt;&lt;span class="sh"&gt;+":(?:"(?:&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="sh"&gt;.|[^"])+"|&lt;/span&gt;&lt;span class="se"&gt;\w&lt;/span&gt;&lt;span class="sh"&gt;+))*)
failregex = ^&lt;/span&gt;&lt;span class="se"&gt;\{&lt;/span&gt;&lt;span class="sh"&gt;%(_groupsre)s,?&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sh"&gt;*"remoteAddr":"&amp;lt;HOST&amp;gt;"%(_groupsre)s,?&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sh"&gt;*"message":"Login failed:
datepattern = ,?&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sh"&gt;*"time"&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sh"&gt;*:&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sh"&gt;*"%%Y-%%m-%%dT%%H:%%M:%%S%%z"
&lt;/span&gt;&lt;span class="no"&gt;F2B
&lt;/span&gt;    &lt;span class="nb"&gt;sudo tee&lt;/span&gt; /etc/fail2ban/jail.local &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;JAIL&lt;/span&gt;&lt;span class="sh"&gt;
[sshd]
enabled = true
maxretry = 4
bantime = 1h

[nextcloud]
enabled = true
port = 80,443
filter = nextcloud
logpath = &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;DATA_DIR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;/nextcloud.log
maxretry = 5
bantime = 1h
&lt;/span&gt;&lt;span class="no"&gt;JAIL
&lt;/span&gt;    &lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable&lt;/span&gt; &lt;span class="nt"&gt;--now&lt;/span&gt; fail2ban
    &lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl restart fail2ban

    occ app:enable password_policy &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null 2&amp;gt;&amp;amp;1 &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true
    &lt;/span&gt;occ config:app:set password_policy minLength &lt;span class="nt"&gt;--value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;12
    occ config:app:set password_policy enforceUpperLowerCase &lt;span class="nt"&gt;--value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1
    occ config:app:set password_policy enforceNumericCharacters &lt;span class="nt"&gt;--value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1
    occ config:app:set password_policy enforceSpecialCharacters &lt;span class="nt"&gt;--value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1
    occ config:app:set password_policy enforceNonCommonPassword &lt;span class="nt"&gt;--value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1
    ok &lt;span class="s2"&gt;"ufw + fail2ban + password policy active"&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;

&lt;span class="c"&gt;# -- done -------------------------------------------------------------------------&lt;/span&gt;
&lt;span class="nb"&gt;echo
&lt;/span&gt;say &lt;span class="s2"&gt;"done. here's the summary:"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"  url:        &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TUNNEL&lt;/span&gt;&lt;span class="p"&gt;,,&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"y"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"https://&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;NC_DOMAIN&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"http://&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;NC_DOMAIN&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"  admin:      &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;ADMIN_USER&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; (password = the one you typed)"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"  data:       &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;DATA_DIR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"  db:         &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;DB&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; (user nextcloud, pass &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;DB_PASS&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; — also in /var/www/nextcloud/config/config.php)"&lt;/span&gt;
&lt;span class="nb"&gt;echo
echo&lt;/span&gt; &lt;span class="s2"&gt;"  phone app: install 'Nextcloud' from the play store / f-droid,"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"  type the url above, log in, enable auto upload. that's it."&lt;/span&gt;
&lt;span class="nb"&gt;echo
echo&lt;/span&gt; &lt;span class="s2"&gt;"  last thing — run this and fix whatever it flags:"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"    sudo -u www-data php /var/www/nextcloud/occ setupchecks"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://clinerds.com/article/self-host-nextcloud-google-photos-alternative" rel="noopener noreferrer"&gt;CLI-NERDS&lt;/a&gt; — practical Linux, DevOps, and self-hosting guides with free browser-based &lt;a href="https://clinerds.com/tools" rel="noopener noreferrer"&gt;developer tools&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>selfhosting</category>
      <category>nextcloud</category>
      <category>privacy</category>
      <category>linux</category>
    </item>
  </channel>
</rss>
