<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Security on AlipourIm journeys</title>
    <link>http://fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion/tags/security/</link>
    <description>Recent content in Security on AlipourIm journeys</description>
    <generator>Hugo -- 0.146.0</generator>
    <language>en</language>
    <lastBuildDate>Mon, 02 Feb 2026 00:00:00 +0000</lastBuildDate>
    <atom:link href="http://fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion/tags/security/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Nextcloud on a Raspberry Pi, Fronted by a Tiny VPS — Nginx Reverse Proxy, Trusted Proxies, and Basic Auth for Jellyfin</title>
      <link>http://fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion/posts/pi_service_touchup/</link>
      <pubDate>Mon, 02 Feb 2026 00:00:00 +0000</pubDate>
      <guid>http://fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion/posts/pi_service_touchup/</guid>
      <description>Cloudflare DNS &#43; Nginx on a VPS &#43; Tailscale to the Pi. Small, boring, and reliable.</description>
      <content:encoded><![CDATA[<blockquote>
<p>I didn’t “move Nextcloud to the cloud”.<br>
I moved the <strong>front door</strong> to a VPS… and kept the house on my Raspberry Pi. 😄</p></blockquote>
<p>This post documents the setup I ended up with:</p>
<ul>
<li>A public <strong>VPS</strong> (host: <code>funbox</code>) running <strong>Nginx</strong> + <strong>Let’s Encrypt</strong></li>
<li>A private <strong>Raspberry Pi</strong> (host: <code>iot-hub</code>) running Docker services (<strong>Nextcloud</strong>, <strong>Jellyfin</strong>, …)</li>
<li>A private backhaul using <strong>Tailscale</strong> (the <code>100.x.y.z</code> network)</li>
<li>A correct Nextcloud reverse-proxy configuration (<strong>trusted_domains</strong>, <strong>trusted_proxies</strong>, and overwrite values)</li>
<li>A pragmatic security layer for media: <strong>Basic Auth at Nginx for Jellyfin</strong><br>
(in addition to Jellyfin’s own login)</li>
</ul>
<p>I’m writing this as a “future me” note and a “copy-paste friendly” guide.</p>
<hr>
<h2 id="0-topology">0) Topology</h2>
<p>The request path looks like:</p>
<pre tabindex="0"><code>Browser
  ↓ HTTPS (public)
Cloudflare DNS (optional proxy on/off)
  ↓
VPS (funbox) — Nginx reverse proxy + Let&#39;s Encrypt
  ↓ HTTP over Tailscale (private 100.x network)
Raspberry Pi (iot-hub) — Docker: Nextcloud / Jellyfin / …
</code></pre><p>Why I like it:</p>
<ul>
<li>The Pi can sit behind home router / CGNAT and still be reachable.</li>
<li>TLS, redirects, headers, auth, rate limits… all centralized on the VPS.</li>
<li>Internal IPs can change without breaking public URLs.</li>
</ul>
<hr>
<h2 id="1-whats-running-on-the-pi">1) What’s running on the Pi?</h2>
<p>On <code>iot-hub</code> the “interesting” containers are:</p>
<ul>
<li><code>nextcloud</code> (Apache variant)</li>
<li><code>nextcloud-db</code> (Postgres)</li>
<li><code>nextcloud-redis</code></li>
<li><code>jellyfin</code></li>
</ul>
<p>Example <code>docker ps</code> style output (yours may vary):</p>
<pre tabindex="0"><code>nextcloud         nextcloud:apache     0.0.0.0:8080-&gt;80/tcp
jellyfin          jellyfin/jellyfin    0.0.0.0:8096-&gt;8096/tcp
</code></pre><p>So on the Pi, the services listen on:</p>
<ul>
<li><code>http://&lt;pi&gt;:8080</code> for Nextcloud</li>
<li><code>http://&lt;pi&gt;:8096</code> for Jellyfin</li>
</ul>
<p>But in my setup, <strong>the VPS does not reach those via LAN</strong> — it reaches them via Tailscale IPs.</p>
<hr>
<h2 id="2-tailscale-the-private-wire-between-vps-and-pi">2) Tailscale: the private wire between VPS and Pi</h2>
<p>Tailscale assigns each node an address like <code>100.xx.yy.zz</code>.</p>
<p>In my config, Nginx on <code>funbox</code> points to the Pi via Tailscale:</p>
<ul>
<li>Nextcloud upstream: <code>http://100.104.127.96:8080</code></li>
<li>Jellyfin upstream: <code>http://100.104.127.96:8096</code></li>
</ul>
<p>(Use the Tailscale IP of your Pi.)</p>
<p>Quick sanity checks from the VPS:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e"># from funbox, make sure you can reach the Pi service:</span>
</span></span><span style="display:flex;"><span>curl -I http://100.104.127.96:8080
</span></span><span style="display:flex;"><span>curl -I http://100.104.127.96:8096
</span></span></code></pre></div><p>If those don’t work: fix Tailscale connectivity first (ACLs, firewall, node online).</p>
<hr>
<h2 id="3-nginx-on-the-vps-reverse-proxy-blocks">3) Nginx on the VPS: reverse proxy blocks</h2>
<h3 id="31-nextcloud-vhost-vps--pi-via-tailscale">3.1 Nextcloud vhost (VPS → Pi via Tailscale)</h3>
<p>Create (or edit):</p>
<p><code>/etc/nginx/sites-available/nextcloud.alipourimjourneys.ir</code></p>
<p>and symlink into <code>sites-enabled</code>.</p>
<p>Here is a complete working example:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-nginx" data-lang="nginx"><span style="display:flex;"><span><span style="color:#75715e"># Redirect HTTP → HTTPS
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">server</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">listen</span> <span style="color:#ae81ff">80</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">listen</span> <span style="color:#e6db74">[::]:80</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">server_name</span> <span style="color:#e6db74">nextcloud.alipourimjourneys.ir</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">return</span> <span style="color:#ae81ff">301</span> <span style="color:#e6db74">https://</span>$host$request_uri;
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">server</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">listen</span> <span style="color:#ae81ff">443</span> <span style="color:#e6db74">ssl</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">listen</span> <span style="color:#e6db74">[::]:443</span> <span style="color:#e6db74">ssl</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">server_name</span> <span style="color:#e6db74">nextcloud.alipourimjourneys.ir</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># Certbot-managed certs
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#f92672">ssl_certificate</span>     <span style="color:#e6db74">/etc/letsencrypt/live/nextcloud.alipourimjourneys.ir/fullchain.pem</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">ssl_certificate_key</span> <span style="color:#e6db74">/etc/letsencrypt/live/nextcloud.alipourimjourneys.ir/privkey.pem</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">include</span>             <span style="color:#e6db74">/etc/letsencrypt/options-ssl-nginx.conf</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">ssl_dhparam</span>         <span style="color:#e6db74">/etc/letsencrypt/ssl-dhparams.pem</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># Big uploads (tune to taste)
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#f92672">client_max_body_size</span> <span style="color:#e6db74">2G</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># CalDAV/CardDAV redirects
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#f92672">location</span> = <span style="color:#e6db74">/.well-known/carddav</span> { <span style="color:#f92672">return</span> <span style="color:#ae81ff">301</span> <span style="color:#e6db74">https://</span>$host/remote.php/dav/; }
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">location</span> = <span style="color:#e6db74">/.well-known/caldav</span>  { <span style="color:#f92672">return</span> <span style="color:#ae81ff">301</span> <span style="color:#e6db74">https://</span>$host/remote.php/dav/; }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">location</span> <span style="color:#e6db74">/</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">proxy_pass</span> <span style="color:#e6db74">http://100.104.127.96:8080</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">proxy_http_version</span> <span style="color:#ae81ff">1</span><span style="color:#e6db74">.1</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">proxy_set_header</span> <span style="color:#e6db74">Host</span>              $host;
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">proxy_set_header</span> <span style="color:#e6db74">X-Real-IP</span>         $remote_addr;
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">proxy_set_header</span> <span style="color:#e6db74">X-Forwarded-For</span>   $proxy_add_x_forwarded_for;
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">proxy_set_header</span> <span style="color:#e6db74">X-Forwarded-Proto</span> $scheme;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#75715e"># Sometimes helps apps behind multiple proxies
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>        <span style="color:#f92672">proxy_set_header</span> <span style="color:#e6db74">X-Forwarded-Host</span>  $host;
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">proxy_set_header</span> <span style="color:#e6db74">X-Forwarded-Port</span>  $server_port;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#75715e"># Nextcloud + WebDAV can do long requests
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>        <span style="color:#f92672">proxy_read_timeout</span> <span style="color:#ae81ff">3600</span>;
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">proxy_send_timeout</span> <span style="color:#ae81ff">3600</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#75715e"># Usually good for DAV/uploads
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>        <span style="color:#f92672">proxy_buffering</span> <span style="color:#66d9ef">off</span>;
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">proxy_request_buffering</span> <span style="color:#66d9ef">off</span>;
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Then test + reload:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo nginx -t
</span></span><span style="display:flex;"><span>sudo systemctl reload nginx
</span></span></code></pre></div><h3 id="32-jellyfin-vhost-with-basic-auth">3.2 Jellyfin vhost (with Basic Auth)</h3>
<p>Create:</p>
<p><code>/etc/nginx/sites-available/jellyfin.alipourimjourneys.ir</code></p>
<p>Example:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-nginx" data-lang="nginx"><span style="display:flex;"><span><span style="color:#66d9ef">server</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">listen</span> <span style="color:#ae81ff">80</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">listen</span> <span style="color:#e6db74">[::]:80</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">server_name</span> <span style="color:#e6db74">jellyfin.alipourimjourneys.ir</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">return</span> <span style="color:#ae81ff">301</span> <span style="color:#e6db74">https://</span>$host$request_uri;
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">server</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">listen</span> <span style="color:#ae81ff">443</span> <span style="color:#e6db74">ssl</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">listen</span> <span style="color:#e6db74">[::]:443</span> <span style="color:#e6db74">ssl</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">server_name</span> <span style="color:#e6db74">jellyfin.alipourimjourneys.ir</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">ssl_certificate</span>     <span style="color:#e6db74">/etc/letsencrypt/live/jellyfin.alipourimjourneys.ir/fullchain.pem</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">ssl_certificate_key</span> <span style="color:#e6db74">/etc/letsencrypt/live/jellyfin.alipourimjourneys.ir/privkey.pem</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">include</span>             <span style="color:#e6db74">/etc/letsencrypt/options-ssl-nginx.conf</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">ssl_dhparam</span>         <span style="color:#e6db74">/etc/letsencrypt/ssl-dhparams.pem</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">client_max_body_size</span> <span style="color:#e6db74">512M</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># ✅ Basic Auth gate (extra layer before Jellyfin)
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    <span style="color:#f92672">auth_basic</span> <span style="color:#e6db74">&#34;Jellyfin</span> <span style="color:#e6db74">(private)&#34;</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">auth_basic_user_file</span> <span style="color:#e6db74">/etc/nginx/.htpasswd-jellyfin</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">location</span> <span style="color:#e6db74">/</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">proxy_pass</span> <span style="color:#e6db74">http://100.104.127.96:8096</span>;
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">proxy_http_version</span> <span style="color:#ae81ff">1</span><span style="color:#e6db74">.1</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">proxy_set_header</span> <span style="color:#e6db74">Host</span>              $host;
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">proxy_set_header</span> <span style="color:#e6db74">X-Real-IP</span>         $remote_addr;
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">proxy_set_header</span> <span style="color:#e6db74">X-Forwarded-For</span>   $proxy_add_x_forwarded_for;
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">proxy_set_header</span> <span style="color:#e6db74">X-Forwarded-Proto</span> $scheme;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#75715e"># Jellyfin uses websockets
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>        <span style="color:#f92672">proxy_set_header</span> <span style="color:#e6db74">Upgrade</span> $http_upgrade;
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">proxy_set_header</span> <span style="color:#e6db74">Connection</span> <span style="color:#e6db74">&#34;upgrade&#34;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">proxy_read_timeout</span> <span style="color:#ae81ff">3600</span>;
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">proxy_send_timeout</span> <span style="color:#ae81ff">3600</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">proxy_buffering</span> <span style="color:#66d9ef">off</span>;
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Enable:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo ln -s /etc/nginx/sites-available/jellyfin.alipourimjourneys.ir /etc/nginx/sites-enabled/
</span></span><span style="display:flex;"><span>sudo nginx -t
</span></span><span style="display:flex;"><span>sudo systemctl reload nginx
</span></span></code></pre></div><hr>
<h2 id="4-creating-the-basic-auth-password-file">4) Creating the Basic Auth password file</h2>
<p>Install tools (Debian/Ubuntu):</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo apt-get update
</span></span><span style="display:flex;"><span>sudo apt-get install -y apache2-utils
</span></span></code></pre></div><p>Create the password file:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo htpasswd -c /etc/nginx/.htpasswd-jellyfin yourusername
</span></span></code></pre></div><p>(If adding more users later, omit <code>-c</code>.)</p>
<p>Lock it down:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo chown root:root /etc/nginx/.htpasswd-jellyfin
</span></span><span style="display:flex;"><span>sudo chmod <span style="color:#ae81ff">640</span> /etc/nginx/.htpasswd-jellyfin
</span></span></code></pre></div><h3 id="notes-on-clients">Notes on clients</h3>
<ul>
<li>Most browsers + most Jellyfin apps handle HTTP Basic Auth fine.</li>
<li>Some TV apps can be quirky. If a client can’t handle it, you can:
<ul>
<li>remove Basic Auth, or</li>
<li>keep it only on selected paths, or</li>
<li>use Cloudflare Access in front of Jellyfin instead (more work).</li>
</ul>
</li>
</ul>
<hr>
<h2 id="5-nextcloud-configure-it-to-behave-behind-the-reverse-proxy">5) Nextcloud: configure it to behave behind the reverse proxy</h2>
<p>Nextcloud needs to know:</p>
<ul>
<li>what hostnames are valid,</li>
<li>which proxy is trusted,</li>
<li>and what the “outside” URL scheme is.</li>
</ul>
<p>You can do it via <code>occ</code> inside the container (Apache image uses <code>www-data</code>).</p>
<h3 id="51-trusted_domains">5.1 trusted_domains</h3>
<p>Check current values:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>docker exec -u www-data nextcloud php /var/www/html/occ config:system:get trusted_domains
</span></span></code></pre></div><p>Add your public domain:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>docker exec -u www-data nextcloud php /var/www/html/occ config:system:set trusted_domains <span style="color:#ae81ff">1</span> --value<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;nextcloud.alipourimjourneys.ir&#34;</span>
</span></span></code></pre></div><p>(Keep your internal name too if you still use it, e.g. <code>rpi:8080</code>.)</p>
<h3 id="52-trusted_proxies">5.2 trusted_proxies</h3>
<p>Because requests arrive from the VPS (over Tailscale), Nextcloud must trust that proxy IP.</p>
<p>Example:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>docker exec -u www-data nextcloud php /var/www/html/occ config:system:set trusted_proxies <span style="color:#ae81ff">0</span> --value<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;100.99.79.75&#34;</span>
</span></span></code></pre></div><p>(Use the <strong>VPS’s Tailscale IP</strong> as seen from the Pi.)</p>
<h3 id="53-overwritehost--overwriteprotocol--overwritecliurl">5.3 overwritehost / overwriteprotocol / overwrite.cli.url</h3>
<p>Tell Nextcloud “the world sees me as <a href="https://nextcloud.example">https://nextcloud.example</a>”:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>docker exec -u www-data nextcloud php /var/www/html/occ config:system:set overwritehost --value<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;nextcloud.alipourimjourneys.ir&#34;</span>
</span></span><span style="display:flex;"><span>docker exec -u www-data nextcloud php /var/www/html/occ config:system:set overwriteprotocol --value<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;https&#34;</span>
</span></span><span style="display:flex;"><span>docker exec -u www-data nextcloud php /var/www/html/occ config:system:set overwrite.cli.url --value<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;https://nextcloud.alipourimjourneys.ir&#34;</span>
</span></span></code></pre></div><h3 id="54-forwarded_for_headers-optional-but-often-helpful">5.4 forwarded_for_headers (optional, but often helpful)</h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>docker exec -u www-data nextcloud php /var/www/html/occ config:system:set forwarded_for_headers <span style="color:#ae81ff">0</span> --value<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;HTTP_X_FORWARDED_FOR&#34;</span>
</span></span></code></pre></div><p>Restart Nextcloud container after config:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>docker restart nextcloud
</span></span></code></pre></div><hr>
<h2 id="6-sanity-checks-curl-is-your-friend">6) Sanity checks (curl is your friend)</h2>
<p>From anywhere public:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>curl -I http://nextcloud.alipourimjourneys.ir
</span></span><span style="display:flex;"><span>curl -I https://nextcloud.alipourimjourneys.ir
</span></span><span style="display:flex;"><span>curl -I https://nextcloud.alipourimjourneys.ir/.well-known/caldav
</span></span></code></pre></div><p>Expected “good signs”:</p>
<ul>
<li>HTTP returns <strong>301</strong> to HTTPS</li>
<li>HTTPS returns <strong>302</strong> to <code>/login</code> (or 200 if already authenticated)</li>
<li><code>/.well-known/caldav</code> returns <strong>301</strong> to <code>/remote.php/dav/</code></li>
</ul>
<p>If you see redirect loops or wrong hostnames:</p>
<ul>
<li>revisit <code>overwritehost</code>, <code>overwriteprotocol</code>, <code>trusted_proxies</code>.</li>
</ul>
<hr>
<h2 id="7-do-i-really-need-cloudflare-access--warp">7) “Do I really need Cloudflare Access / WARP?”</h2>
<h3 id="the-honest-answer">The honest answer</h3>
<p>If your setup is:</p>
<ul>
<li>HTTPS only</li>
<li>strong passwords + MFA in Nextcloud/Jellyfin</li>
<li>your origin isn’t directly exposed (only the VPS is public)</li>
<li>you keep things patched</li>
</ul>
<p>…then you’re already in a <strong>reasonable</strong> place.</p>
<h3 id="can-i-skip-cloudflare-access">“Can I skip Cloudflare Access?”</h3>
<p>Yes. In this topology, Cloudflare Access is optional. The main security boundary is:</p>
<ul>
<li><strong>Public:</strong> VPS + Nginx</li>
<li><strong>Private:</strong> Pi over Tailscale</li>
</ul>
<p>For Jellyfin, Basic Auth adds a cheap extra gate that’s “family friendly”.</p>
<hr>
<h2 id="8-cloudflare-access-one-time-pin-not-arriving--passkeys">8) Cloudflare Access: One-time PIN not arriving + passkeys</h2>
<p>Two common gotchas:</p>
<h3 id="81-one-time-pin-email-didnt-arrive">8.1 One-time PIN email didn’t arrive</h3>
<p>That flow relies on email delivery. Check:</p>
<ul>
<li>spam/junk folder</li>
<li>if your provider blocked it</li>
<li>the exact email allowlist in your policy</li>
</ul>
<p>If it’s flaky, I’d avoid One-time PIN and use a real identity provider.</p>
<h3 id="82-can-i-use-passkeys--apple--google">8.2 Can I use passkeys / Apple / Google?</h3>
<p>Yes — but passkeys typically come via an identity provider (IdP) that supports WebAuthn/passkeys.
Practical approach:</p>
<ul>
<li>pick what your family already uses (Google or Apple),</li>
<li>configure that as the login method,</li>
<li>avoid WARP enrollment unless you specifically want device-based access.</li>
</ul>
<hr>
<h2 id="9-hardening-checklist-tiny-but-effective">9) Hardening checklist (tiny but effective)</h2>
<p>On the VPS:</p>
<ul>
<li>Keep Ubuntu security updates on</li>
<li>firewall: allow only what you need (22/80/443)</li>
<li>optional: <code>fail2ban</code> for SSH</li>
</ul>
<p>On the Pi:</p>
<ul>
<li>keep Docker images updated</li>
<li>Postgres/Redis not exposed publicly (Docker internal network)</li>
<li>backups: Nextcloud data + DB</li>
</ul>
<hr>
<h2 id="10-tldr">10) TL;DR</h2>
<ul>
<li>VPS Nginx terminates TLS, proxies to Pi over Tailscale</li>
<li>Nextcloud must be told about:
<ul>
<li><code>trusted_domains</code></li>
<li><code>trusted_proxies</code></li>
<li>overwrite values (<code>overwritehost</code>, <code>overwriteprotocol</code>, <code>overwrite.cli.url</code>)</li>
</ul>
</li>
<li><code>curl -I</code> should show sane redirects + <code>/remote.php/dav/</code></li>
<li>Jellyfin gets an extra gate with <strong>Nginx Basic Auth</strong></li>
</ul>
<p>Boring is good. Boring runs for months.</p>
<hr>
<hr>
<div class="signature-block" style="margin-top:1rem">
  <p><strong>Downloads:</strong>
    <a href="/sources/posts/pi_service_touchup.md">Markdown</a> ·
    <a href="/sources/posts/pi_service_touchup.md.asc">Signature (.asc)</a>
  </p><details class="signature">
    <summary>View OpenPGP signature</summary>
    <pre style="font-size:0.85em; overflow-x:auto; padding:0.75rem;">-----BEGIN PGP SIGNATURE-----

iQIzBAABCgAdFiEEVaKl3oR5K6zGyu4/tYgoUOBMjSoFAmpkbuYACgkQtYgoUOBM
jSomZA/&#43;LsB&#43;V0BnPki5qaDc&#43;tRu6EXM5kPuH7G8x5ar4opsKgbfsRKEwT6/Q0Er
vfg48uYNp4fBnoyfJrgURx&#43;OwS7EVJRCmXaRsdilEEGHF7cT3qHhlczYaC&#43;58lGs
&#43;qXaHjYE8x0byAZ8iHNxNUhIyILVrcsdua3JZ3D3J/8r93dfVgrWg41Nrtj4tPUE
QQMW/KxTe/TOED4iivXxFlDCiT13KmgB/B9HeunGPqu8lPMTORf4ePoPzeYEeuuZ
iVH&#43;ssNZ9XB00Z4a/c3I0d2ALLYoA/dXmrJkl0qCCpCy&#43;7/id2yz3eXYWn2xKMvp
SAMTYaFAV6Fd7xdmxGYEeoCSDu8P57P7QfdPVEU1oUTANO21tEh1vGnjxcFdJUBx
kOvBdjQf4wDqUuNv7NKA&#43;OHOzhJ3Wf3VLb/ZNq6Y2okEibsCygm3XDn0008WeKPv
ncB0gceY6nFYzbyhuUIhPtQJJWDNi5KG/KMEvYcebEwDzn7TErg/v3Bp8ZCdWRx/
Gs8K9nADnHhAjWgwTq3D&#43;2qRWcF0tlLSTmKg&#43;95yaYi0XWWMFGTgCv2odPsgFlIS
3FiLJC3rV73prsk&#43;7eZftBTYCJN0Xk92YFj4a6bTYeuLcC20VbAA98Bpi0pCyO0v
TJ2&#43;amlKyT&#43;Nq9wGrAez&#43;dTvR0FKuEvA5OO693Aibv/iwOX6UPU=
=2UUg
-----END PGP SIGNATURE-----
</pre>
  </details><p><em>Verify locally:</em></p>
  <pre style="font-size:0.85em; overflow-x:auto; padding:0.75rem;">torsocks curl -fSLO http://fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion/sources/posts/pi_service_touchup.md
torsocks curl -fSLO http://fjthpp2h3mj2rup25r3psmqamutnkbvxbpltlohdthw6fscgo3t6bpad.onion/sources/posts/pi_service_touchup.md.asc
gpg --verify pi_service_touchup.md.asc pi_service_touchup.md
  </pre>
</div>


]]></content:encoded>
    </item>
  </channel>
</rss>
