<?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>Solution on gvisoc.com</title>
    <link>https://gvisoc.com/en/tags/solution/</link>
    <description>Recent content in Solution on gvisoc.com</description>
    <image>
      <title>gvisoc.com</title>
      <url>https://gvisoc.com/images/site-umina-gs.jpg</url>
      <link>https://gvisoc.com/images/site-umina-gs.jpg</link>
    </image>
    <generator>Hugo -- 0.151.0</generator>
    <language>en-AU</language>
    <managingEditor>gabriel@gvisoc.com (Gabriel Viso Carrera)</managingEditor>
    <webMaster>gabriel@gvisoc.com (Gabriel Viso Carrera)</webMaster>
    <copyright>Gabriel Viso Carrera</copyright>
    <lastBuildDate>Sun, 07 Sep 2025 07:49:35 +0000</lastBuildDate>
    <atom:link href="https://gvisoc.com/en/tags/solution/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Forward TCP and UDP Traffic Across the Network With iptables</title>
      <link>https://gvisoc.com/en/posts/iptables-router/</link>
      <pubDate>Sun, 07 Sep 2025 07:49:35 +0000</pubDate><author>gabriel@gvisoc.com (Gabriel Viso Carrera)</author>
      <guid>https://gvisoc.com/en/posts/iptables-router/</guid>
      <description>This method enables routing TCP and UDP traffic between machines when no proxy is needed, or solutions based in proxies fail</description>
      <content:encoded><![CDATA[<p>This is the third post of the series that started with &ldquo;<a href="https://gvisoc.com/en/posts/changes-xmpp/">Changes to My Network, Sponsored By XMPP</a>&rdquo;.</p>
<h2 id="the-problem">The Problem</h2>
<p>In that article, nearly at the very end, I was pointing out that a solution was needed for the XMPP traffic on non-Web ports (TCP and UDP other than 80 and 443) to be routed from a cloud VPS to my home server.</p>
<p>I tried some NGINX configuration using its module <code>streams</code>, but it didn&rsquo;t work, so I will post here how I ended up solving that using Linux&rsquo; &ndash;the kernel itself&ndash; packet filter rules through <a href="https://www.netfilter.org/projects/iptables/index.html">iptables</a>.</p>
<p>I learned and adapted my solution from the content of this <a href="https://ryanwelch.me/blog/port-forward-with-tailscale/">article</a> by <a href="https://ryanwelch.me">Ryan Welch</a>.</p>
<figure class="ma0 w-75">
    <img loading="lazy" src="/images/20250910-solution.png"
         alt="The diagram that represents the goal we&#39;ve just went through."/> <figcaption>
            <p>The goal</p>
        </figcaption>
</figure>

<p>At the end of the post, though, I will also post how to configure NGINX to work with TCP and UDP general traffic, just in case we need it later, or maybe you want to try it and tell me what I did wrong.</p>
<h2 id="solution">Solution</h2>
<p>As explained before, Linux (NOT GNU/Linux, but just &ldquo;Linux&rdquo;, the kernel) has network packet filtering capabilities built-in that can be configured to our needs.</p>
<p>To configure the VPS to do it, SSH into the machine and enable IP forwarding in the kernel:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="go">sudo sysctl -w net.ipv4.ip_forward=1
</span></span></span></code></pre></div><p>This may or may not be persitent between boots, depending on your distribution. To be sure, uncomment the line <code>net.ipv4.ip_forward=1</code> in <code>/etc/sysctl.conf</code>.</p>
<p>Then, you will be able to redirect ports and ranges with sentences like the following:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="go">iptables -A PREROUTING -t nat -p tcp -i eth0 --dport 5222 -j DNAT --to-destination &lt;Upstream VPN IP&gt;:5222
</span></span></span><span class="line"><span class="cl"><span class="go">iptables -A POSTROUTING -t nat -p tcp -d &lt;Upstream VPN IP&gt; --dport 5222 -j MASQUERADE
</span></span></span></code></pre></div><p>And, for ranges of ports:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="go">iptables -A PREROUTING -t nat -p udp -i eth0 -m multiport --dports 49152:65535 -j DNAT --to-destination &lt;Upstream VPN IP&gt;:49152-65535
</span></span></span><span class="line"><span class="cl"><span class="go">iptables -A POSTROUTING -t nat -p udp -d &lt;Upstream VPN IP&gt; -m multiport --dports 49152:65535 -j MASQUERADE
</span></span></span></code></pre></div><p>You&rsquo;ll have to change the ports in this example, as well as putting your own remote IP address instead of the <code>&lt;Upstream VPN IP&gt;</code> placeholder.</p>
<p>Add all the forwarding rules corresponding to all the XMPP server ports, where <code>&lt;Upstream VPN IP&gt;</code> would be the Tailscale IP of my home server, the one we were calling <em>potato</em> in <a href="https://gvisoc.com/en/posts/vps-homeserver/">the previous article of the series</a>):</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="go">iptables -A PREROUTING -t nat -p tcp -i eth0 --dport 5222 -j DNAT --to-destination &lt;Upstream VPN IP&gt;:5222
</span></span></span><span class="line"><span class="cl"><span class="go">iptables -A POSTROUTING -t nat -p tcp -d &lt;Upstream VPN IP&gt; --dport 5222 -j MASQUERADE
</span></span></span><span class="line"><span class="cl"><span class="go">iptables -A PREROUTING -t nat -p tcp -i eth0 --dport 5269 -j DNAT --to-destination &lt;Upstream VPN IP&gt;:5269
</span></span></span><span class="line"><span class="cl"><span class="go">iptables -A POSTROUTING -t nat -p tcp -d &lt;Upstream VPN IP&gt; --dport 5269 -j MASQUERADE
</span></span></span><span class="line"><span class="cl"><span class="go">iptables -A PREROUTING -t nat -p tcp -i eth0 --dport 5000 -j DNAT --to-destination &lt;Upstream VPN IP&gt;:5000
</span></span></span><span class="line"><span class="cl"><span class="go">iptables -A POSTROUTING -t nat -p tcp -d &lt;Upstream VPN IP&gt; --dport 5000 -j MASQUERADE
</span></span></span><span class="line"><span class="cl"><span class="go">iptables -A PREROUTING -t nat -p tcp -i eth0 --dport 3478 -j DNAT --to-destination &lt;Upstream VPN IP&gt;:3478
</span></span></span><span class="line"><span class="cl"><span class="go">iptables -A POSTROUTING -t nat -p tcp -d &lt;Upstream VPN IP&gt; --dport 3478 -j MASQUERADE
</span></span></span><span class="line"><span class="cl"><span class="go">iptables -A PREROUTING -t nat -p tcp -i eth0 --dport 3479 -j DNAT --to-destination &lt;Upstream VPN IP&gt;:3479
</span></span></span><span class="line"><span class="cl"><span class="go">iptables -A POSTROUTING -t nat -p tcp -d &lt;Upstream VPN IP&gt; --dport 3479 -j MASQUERADE
</span></span></span><span class="line"><span class="cl"><span class="go">iptables -A PREROUTING -t nat -p udp -i eth0 --dport 3478 -j DNAT --to-destination &lt;Upstream VPN IP&gt;:3478
</span></span></span><span class="line"><span class="cl"><span class="go">iptables -A POSTROUTING -t nat -p udp -d &lt;Upstream VPN IP&gt; --dport 3478 -j MASQUERADE
</span></span></span><span class="line"><span class="cl"><span class="go">iptables -A PREROUTING -t nat -p udp -i eth0 --dport 3479 -j DNAT --to-destination &lt;Upstream VPN IP&gt;:3479
</span></span></span><span class="line"><span class="cl"><span class="go">iptables -A POSTROUTING -t nat -p udp -d &lt;Upstream VPN IP&gt; --dport 3479 -j MASQUERADE
</span></span></span><span class="line"><span class="cl"><span class="go">iptables -A PREROUTING -t nat -p tcp -i eth0 --dport 5349 -j DNAT --to-destination &lt;Upstream VPN IP&gt;:5349
</span></span></span><span class="line"><span class="cl"><span class="go">iptables -A POSTROUTING -t nat -p tcp -d &lt;Upstream VPN IP&gt; --dport 5349 -j MASQUERADE
</span></span></span><span class="line"><span class="cl"><span class="go">iptables -A PREROUTING -t nat -p tcp -i eth0 --dport 5350 -j DNAT --to-destination &lt;Upstream VPN IP&gt;:5350
</span></span></span><span class="line"><span class="cl"><span class="go">iptables -A POSTROUTING -t nat -p tcp -d &lt;Upstream VPN IP&gt; --dport 5350 -j MASQUERADE
</span></span></span><span class="line"><span class="cl"><span class="go">iptables -A PREROUTING -t nat -p udp -i eth0 --dport 5349 -j DNAT --to-destination &lt;Upstream VPN IP&gt;:5349
</span></span></span><span class="line"><span class="cl"><span class="go">iptables -A POSTROUTING -t nat -p udp -d &lt;Upstream VPN IP&gt; --dport 5349 -j MASQUERADE
</span></span></span><span class="line"><span class="cl"><span class="go">iptables -A PREROUTING -t nat -p udp -i eth0 --dport 5350 -j DNAT --to-destination &lt;Upstream VPN IP&gt;:5350
</span></span></span><span class="line"><span class="cl"><span class="go">iptables -A POSTROUTING -t nat -p udp -d &lt;Upstream VPN IP&gt; --dport 5350 -j MASQUERADE
</span></span></span><span class="line"><span class="cl"><span class="go">iptables -A PREROUTING -t nat -p udp -i eth0 -m multiport --dports 49152:65535 -j DNAT --to-destination &lt;Upstream VPN IP&gt;:49152-65535
</span></span></span><span class="line"><span class="cl"><span class="go">iptables -A POSTROUTING -t nat -p udp -d &lt;Upstream VPN IP&gt; -m multiport --dports 49152:65535 -j MASQUERADE
</span></span></span></code></pre></div><p>Test the set up, and when everything works, save the iptables rules by <code>sudo iptables-save &gt; /etc/iptables/rules.v4</code> so that these rules are applied at every boot of the VPS.</p>
<p>In <a href="https://ryanwelch.me/blog/port-forward-with-tailscale/">his article</a>, Ryan adds more considerations for things like congestion control and such. Check it out to know more and apply whatever suits your needs.</p>
<h2 id="bonus-nginx-with-generic-tcpudp-traffic">Bonus: NGINX With Generic TCP/UDP Traffic</h2>
<blockquote>
<p>Take this with a pinch of salt: as I stated at the beginning of the post, this didn&rsquo;t work for me and my XMPP server of choice, <a href="https://snikket.org/">Snikket</a>. However, according to NGINX&rsquo; documentation, this is the way it is configured for handling non-Web traffic.</p></blockquote>
<p>Install <code>libnginx-mod-stream</code> to support UDP and TCP reverse proxies.</p>
<p>Modify <code>/etc/nginx/nginx.conf</code> to add a stream block:</p>
<pre tabindex="0"><code class="language-conf" data-lang="conf">stream {
        include /etc/nginx/streams-enabled/*;
}
</code></pre><p>Then, create the directories following the existing schema NGINX has for webs: <code>sudo mkdir -p /etc/nginx/streams-available /etc/nginx/streams-enabled</code></p>
<p>Create an <code>/etc/nginx/streams-available/tcpudpservice.example.com.conf</code> with rules like the following:</p>
<pre tabindex="0"><code class="language-conf" data-lang="conf"># TCP Port (non-HTTP traffic):
server {
        listen 5222;
        proxy_pass &lt;Upstream VPN IP&gt;:5222;
}

# ...

# UDP Port:
server {
        listen 3478 udp;
        proxy_pass &lt;Upstream VPN IP&gt;:3478;
}

# ...

# Ranges are accepted, too. In this case, note the
# use of $server_port in the proxy_pass sentence.

server {
        listen 49152-65535 udp;
        proxy_pass &lt;Upstream VPN IP&gt;:$server_port;
}
</code></pre><p>In the case of defining very large range of ports, such as the ranges that Snikket proposes for XMPP audio and video services in multi-users configurations, NGINX will throw an error due to having too many files open. Snikket expects to be able to establish 16384 UDP connections.</p>
<p>If you want to solve this instead of reducing the range of UDP ports, we must increase the <code>LimitNOFILE</code> parameter of NGINX in systemd by doing <code>sudo systemctl edit nginx.service</code>. This will open an editor for a drop-in extension to the service unit.</p>
<p>Write the following and save the file:</p>
<pre tabindex="0"><code class="language-unit" data-lang="unit">[Service]
LimitNOFILE=65535
</code></pre><p>Then, in the preamble of the <code>/etc/nginx/nginx.conf</code> (outside of any <code>http</code>, <code>events</code> or <code>stream</code>), add a line <code>worker_rlimit_nofile</code> with a value less than 65535 (30000 will work for the Snikket case), and increase the <code>worker_connections</code> inside <code>events</code>:</p>
<pre tabindex="0"><code class="language-conf" data-lang="conf">...
include /etc/nginx/modules-enabled/*.conf;

worker_rlimit_nofile 30000;

events {
        # worker_connections 768;
        # 3 times 8192 will suffice   
        worker_connections 24576;
        # multi_accept on;
}

...
</code></pre><p>As I said before, this configuration did not work with XMPP, but it can be useful for other use cases.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Exposing Services Hosted at Home Through a VPS and Tailscale</title>
      <link>https://gvisoc.com/en/posts/vps-homeserver/</link>
      <pubDate>Fri, 05 Sep 2025 05:40:00 +1000</pubDate><author>gabriel@gvisoc.com (Gabriel Viso Carrera)</author>
      <guid>https://gvisoc.com/en/posts/vps-homeserver/</guid>
      <description>An easy way of exposing services hosted at your home network without having to open ports on your router and without the need of a static IP address</description>
      <content:encoded><![CDATA[<h2 id="the-problem">The Problem</h2>
<p>This text shows the steps to follow to connect an <a href="https://nginx.org">NGINX</a> reverse proxy installed on a server A, and provide access through it to services hosted in a different machine, B. If the machines are in different locations or networks, we can solve the connectivity in several ways, one of them being a VPN.</p>
<p>In my personal case, this allows me to install NGINX on a VPS and use it to expose services hosted at my home server, &ldquo;barcas&rdquo;. To connect the two machines and allow NGINX to reach the upstream services, I have used <a href="https://tailscale.com">Tailscale</a>.</p>
<figure class="ma0 w-75">
    <img loading="lazy" src="/images/20250905-solution.png"
         alt="The diagram that represents the goal we&#39;ve just went through."/> <figcaption>
            <p>The goal</p>
        </figcaption>
</figure>

<p>This post continues the series that started with &ldquo;<a href="/en/posts/changes-xmpp">Changes to My Network, Sponsored by XMPP</a>&rdquo;. If you&rsquo;re wondering why would we do that, or what value brings this configuration, please check read that one first.</p>
<h2 id="solution">Solution</h2>
<p>You&rsquo;ll need the following:</p>
<ol>
<li>A VPS server rented from your provider of choice, to which you can access through SSH.</li>
<li>A server at your home, with at least one service accepting HTTP connections, let&rsquo;s say, through the port 5000. (<code>0.0.0.0:5000</code>). I will call this host <em>potato</em> in the remainder of this post, just for the sake of brevity and readability.</li>
<li>A free <a href="https://tailscale.com">Tailscale</a> account. In general, we could do this with any VPN provider and even one self-hosted by us, preferably providing service based on <a href="https://en.wikipedia.org/wiki/WireGuard">WireGuard</a>, as it is more performant than <a href="https://es.wikipedia.org/wiki/OpenVPN">OpenVPN</a>.</li>
<li>Optionally, or probably for this post to be meaningful, we should have a domain with their DNS records pointing to the VPS of point #1.</li>
</ol>
<p>For the rest of the article I will assume that the hosts (the VPS and <em>potato</em>) have installed an Ubuntu LTS Server, such as 24.04.</p>
<p>If there were no issues you should have all this up and running in about 20 minutes.</p>
<h3 id="home-server">Home Server</h3>
<p>We&rsquo;ll log into (the) <em>potato</em>, where our service is waiting for requests behind port 5000.</p>
<p><a href="https://tailscale.com/download">Install Tailscale</a> and authenticate <em>potato</em> against our account. This will give <em>potato</em> an IP address within the VPN:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">#</span> Tailscale install commands are not reproduced, as they can 
</span></span><span class="line"><span class="cl"><span class="gp">#</span> change at any point in time.
</span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="go">sudo tailscale up
</span></span></span><span class="line"><span class="cl"><span class="go"></span><span class="gp">#</span> Follow the instructions
</span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="gp">#</span> Enable Tailscale as a system service, permanently running
</span></span><span class="line"><span class="cl"><span class="go">sudo systemctl enable tailscaled --now
</span></span></span><span class="line"><span class="cl"><span class="go"></span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="gp">#</span> Obtain and write down the IP address at the tailnet.
</span></span><span class="line"><span class="cl"><span class="go">tailscale ip -4
</span></span></span><span class="line"><span class="cl"><span class="go">100.85.67.22
</span></span></span></code></pre></div><p>At Tailscale&rsquo;s dashboard we can see the host&rsquo;s local name, and also the IP address returned by the last command.</p>
<p>We can also configure the host so that we don&rsquo;t have to re-authenticate it into the VPN, disabling the expiry of the key used to authenticate the machine to Tailscale.</p>
<figure class="ma0 w-10">
    <img loading="lazy" src="/images/20250905-keyexpiry.png"
         alt="A context menu option of the Tailscale control panel that disables the key expiry"/> <figcaption>
            <p>Relevant option to disable the key expiry</p>
        </figcaption>
</figure>

<p>Optionally, we can also enable &ldquo;MagicDNS&rdquo; to be able to refer to the hosts by a domain name instead of by an IP address, with no local configuration to do in the machines.</p>
<figure class="ma0 w-75">
    <img loading="lazy" src="/images/20250905-magicdns.png"
         alt="&#34;MagicDNS&#34; options in Tailscale&#39;s control panel, shown as &#34;Automatically register domain names for devices in your tailnet. This lets you to use a machine’s name instead of its IP address.&#34;. In the picture it is already enabled."/> <figcaption>
            <p>&ldquo;MagicDNS&rdquo; options in Tailscale&rsquo;s control panel. In the picture it is already enabled.</p>
        </figcaption>
</figure>

<p>Once the machine is already in Tailscale, we must <strong>open port 5000 in the <em>potato</em>&rsquo;s firewall</strong>, if we hadn&rsquo;t done so yet, so that it can receive requests from the VPS through that port.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="go">sudo iptables -A INPUT -p tcp -m tcp --dport 5000 -j ACCEPT
</span></span></span></code></pre></div><p>Regarding this:</p>
<ol>
<li>If your machine doesn&rsquo;t have <code>iptables-persistent</code> installed, this is a good moment to install it so that we can save the firewall rules to be applied automatically at boot time.</li>
</ol>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="go">sudo apt install iptables-persistent 
</span></span></span><span class="line"><span class="cl"><span class="go">sudo iptables-save &gt; /etc/iptables/rules.v4
</span></span></span></code></pre></div><ol start="2">
<li>I personally open the ports in all the network interfaces, not only in Tailscale&rsquo;s.
<ul>
<li>As I won&rsquo;t have that port open in the router, I am not doing anything that would leave me overly exposed.</li>
<li>This way I don&rsquo;t need to always use Tailscale to perform tests on <em>potato</em> from home.</li>
<li>Each case is different: you should decide what to do with your firewall depending on how critical the service you&rsquo;re configuring is, and how sensitive the data it manages is.</li>
</ul>
</li>
</ol>
<h3 id="vps">VPS</h3>
<p>Connect to the VPS through SSH and install a reverse proxy. Even if we&rsquo;re not going to have more than one web (80, 443) service
listening, configuring, understanding and managing an HTTP reverse proxy is way easier than handle IP redirects and their relevant firewall DNAT rules.</p>
<p>I use NGINX.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="go">sudo apt update &amp;&amp; sudo apt install nginx
</span></span></span></code></pre></div><p><a href="https://tailscale.com/download">Install Tailscale</a> and follow all the steps we did with <em>potato</em>. Write down the VPS&rsquo; IP address in &ldquo;the tailnet&rdquo;, for example <code>100.85.77.36</code>.</p>
<p>Get a Let&rsquo;s Encrypt certificate your your domain, with CertBot. For this:</p>
<ul>
<li>We must have already configured the DNS records of our domain, <code>example.com</code>, to point to the public IP of the VPS (not its IP at Tailscale) we&rsquo;re working on.</li>
<li>We may have to stop NGINX (<code>sudo systemctl stop nginx</code>) before, depending on our domain provider and their CertBot plugins available, if any.</li>
</ul>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="go">sudo certbot certonly --standalone -d example.com
</span></span></span><span class="line"><span class="cl"><span class="go">...
</span></span></span><span class="line"><span class="cl"><span class="go">...
</span></span></span><span class="line"><span class="cl"><span class="go">Successfully received certificate.
</span></span></span><span class="line"><span class="cl"><span class="go">Certificate saved at: /etc/letsencrypt/live/example.com/fullchain.pem;
</span></span></span><span class="line"><span class="cl"><span class="go">Key is saved at:      /etc/letsencrypt/live/example.com/privkey.pem;
</span></span></span><span class="line"><span class="cl"><span class="go">...
</span></span></span><span class="line"><span class="cl"><span class="go">...
</span></span></span></code></pre></div><p>Configure now the service in the reverse proxy, writing the IP of <em>potato</em> in Tailscale in the <code>proxy_pass</code> directive. For this, we&rsquo;ll create a configuration file under <code>/etc/nginx/sites-available/</code>, for example <code>example.com.conf</code> for an <code>https://example.com</code> website.</p>
<pre tabindex="0"><code class="language-conf" data-lang="conf">upstream exampleweb {
        # Replace [IP or host name] for your &#39;potato&#39; value
        # **in Tailscale**.
        # 100.85.67.22 in the post&#39;s example.
        server [IP or host name]:5000;
        keepalive 64;
}

server {
        server_name example.com;
        listen 80;

        location / {
                return 301 https://$host$request_uri;
        }
}

server {
        server_name example.com;
        listen 443 ssl http2;

        # Other SSL stuff goes here
        ssl_protocols TLSv1.2 TLSv1.3;
        ssl_ciphers HIGH:!MEDIUM:!LOW:!aNULL:!NULL:!SHA;
        ssl_prefer_server_ciphers on;
        ssl_session_cache shared:SSL:10m;
        ssl_session_tickets off;

        ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

        location / {
                # The final `/` is important.
                proxy_pass http://exampleweb/;
                add_header X-Frame-Options SAMEORIGIN;
                add_header X-XSS-Protection &#34;1; mode=block&#34;;
                proxy_redirect off;
                proxy_buffering off;
                proxy_set_header Host $host;
                proxy_set_header X-Real-IP $remote_addr;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_set_header X-Forwarded-Proto $scheme;
                proxy_set_header X-Forwarded-Port $server_port;
                proxy_read_timeout 90;
        }
}
</code></pre><p>We&rsquo;ll create now a symbolic link to this file under <code>/etc/nginx/sites-enabled</code>, test NGINX&rsquo; configuration and, if all goes well, reload it.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="go">cd /etc/nginx/sites-enabled/
</span></span></span><span class="line"><span class="cl"><span class="go">sudo ln -s ../sites-available/example.com.conf
</span></span></span><span class="line"><span class="cl"><span class="go">sudo nginx -t
</span></span></span><span class="line"><span class="cl"><span class="go">nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
</span></span></span><span class="line"><span class="cl"><span class="go">nginx: configuration file /etc/nginx/nginx.conf test is successful
</span></span></span><span class="line"><span class="cl"><span class="go"></span><span class="gp">#</span> If we had to stop NGINX to get the certificate, we start it up<span class="p">;</span> 
</span></span><span class="line"><span class="cl"><span class="gp">#</span> otherwise:
</span></span><span class="line"><span class="cl"><span class="go">sudo systemctl reload nginx
</span></span></span></code></pre></div><p>At this point we should be able to interact with our service at <em>potato</em> through <code>https://example.com</code>.</p>
<h2 id="troubleshooting">Troubleshooting</h2>
<ul>
<li><strong>This thing doesn&rsquo;t render anything, the service seems to be not replying</strong>. If there is activity in NGINX&rsquo; logs in the VPS (<code>sudo tail -f /var/log/nginx/access.log</code>), and the request is abandoned after a time out, there are a few possible causes for this in my limited experience. At <em>potato</em>:
<ul>
<li>The service upstream we want to access from NGINX is not working, and we have to start it up.</li>
<li>the service upstream is exposed through port 5000 of a specific network interface, instead of <code>0.0.0.0</code> (which means <code>all network interfaces</code> of <em>potato</em>), and other than Tailscale&rsquo;s; for example, <code>127.0.0.1</code>,</li>
<li>or we did something wrong in the firewall and the request is being rejected in that way.</li>
</ul>
</li>
<li><strong>NGINX&rsquo; settings are OK</strong> (<code>sudo nginx -t</code> says 👍)<strong>, but NGINX doesn&rsquo;t start</strong> or dies when you reload the configuration. The most frequent cause is that NGINX can&rsquo;t reach or find the upstream at <em>potato</em>, the one pointed by the <code>proxy_pass</code> directive.
<ul>
<li>We may have a typo in the <code>proxy_pass</code>,</li>
<li>NGINX may have tried to reach <em>potato</em> before Tailscale&rsquo;s tunnel was up, for example if this happens upon a VPS reboot,</li>
<li>maybe Tailscale is down in any of the two hosts.</li>
<li>This cause can be checked by looking for a <code>host not found in upstream</code> in the output of <code>sudo systemctl status nginx</code>:</li>
</ul>
</li>
</ul>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="go">nginx.service - A high performance web server and a reverse proxy server
</span></span></span><span class="line"><span class="cl"><span class="go">     Loaded: loaded (/usr/lib/systemd/system/nginx.service; enabled; preset: enabled)
</span></span></span><span class="line"><span class="cl"><span class="go">    Drop-In: /etc/systemd/system/nginx.service.d
</span></span></span><span class="line"><span class="cl"><span class="go">             └─override.conf
</span></span></span><span class="line"><span class="cl"><span class="go">     Active: active (running) since Fri 2025-08-29 11:05:51 UTC; 3 days ago
</span></span></span><span class="line"><span class="cl"><span class="go">       Docs: man:nginx(8)
</span></span></span><span class="line"><span class="cl"><span class="go">    Process: 1154 ExecStartPre=/usr/sbin/nginx -t -q -g daemon on; master_process on; (code=exited, status=0/SUCCESS)
</span></span></span><span class="line"><span class="cl"><span class="go">    Process: 1156 ExecStart=/usr/sbin/nginx -g daemon on; master_process on; (code=exited, status=0/SUCCESS)
</span></span></span><span class="line"><span class="cl"><span class="go">    Process: 58676 ExecReload=/usr/sbin/nginx -g daemon on; master_process on; -s reload (code=exited, status=1/FAILURE)
</span></span></span><span class="line"><span class="cl"><span class="go">   Main PID: 1157 (nginx)
</span></span></span><span class="line"><span class="cl"><span class="go">      Tasks: 3 (limit: 1110)
</span></span></span><span class="line"><span class="cl"><span class="go">     Memory: 46.9M (peak: 200.2M)
</span></span></span><span class="line"><span class="cl"><span class="go">        CPU: 29min 57.349s
</span></span></span><span class="line"><span class="cl"><span class="go">     CGroup: /system.slice/nginx.service
</span></span></span><span class="line"><span class="cl"><span class="go">             ├─1157 &#34;nginx: master process /usr/sbin/nginx -g daemon on; master_process on;&#34;
</span></span></span><span class="line"><span class="cl"><span class="go">             ├─1158 &#34;nginx: worker process&#34;
</span></span></span><span class="line"><span class="cl"><span class="go">             └─1159 &#34;nginx: cache manager process&#34;
</span></span></span><span class="line"><span class="cl"><span class="go"></span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="go">...
</span></span></span><span class="line"><span class="cl"><span class="go">Sep 02 05:32:49 vps-hostingprovider nginx[58676]: 2025/09/02 05:32:49 [emerg] 58676#58676: host not found in upstream &#34;100.85.77.22&#34; in /etc/nginx/sites-enabled/example.com.conf&gt;
</span></span></span><span class="line"><span class="cl"><span class="go">Sep 02 05:32:49 vps-hostingprovider systemd[1]: nginx.service: Control process exited, code=exited, status=1/FAILURE
</span></span></span><span class="line"><span class="cl"><span class="go">Sep 02 05:32:49 vps-hostingprovider systemd[1]: Reload failed for nginx.service - A high performance web server and a reverse proxy server.
</span></span></span><span class="line"><span class="cl"><span class="go">lines 1-28/28 (END)
</span></span></span></code></pre></div><ul>
<li><strong>We get an HTTP Status Code 502</strong>. This means that, while NGINX is doing well, the upstream service we&rsquo;re trying to reach at <em>potato</em>&rsquo;s port 500 is throwing errors.
<ul>
<li>If you go and do a test from the <em>potato</em> and goes well, almost for sure this is a matter of <strong>trusted proxies</strong>. To describe the problem in depth and correctly is outside of the scope of this post, but in summary the point is that you must tell your application that the VPS that is hosting the reverse proxy, a machine other than <em>potato</em>, is trustworthy. This is done by configuring VPS&rsquo; IP address in Tailscale as a trusted proxy. <strong>Check your specific web application logs</strong> to confirm this hypothesis.</li>
<li>This is not a problem, but a rather interesting security feature.</li>
<li>For example, this happened to me with both my Mastodon instance and Nextcloud. In both cases, each application&rsquo;s documentation had the solution for it: <a href="https://docs.joinmastodon.org/admin/config/#trusted_proxy_ip">Mastodon &ndash; trusted proxies</a>, <a href="https://docs.nextcloud.com/server/latest/admin_manual/configuration_server/reverse_proxy_configuration.html#defining-trusted-proxies">Nextcloud &ndash; trusted proxies</a></li>
<li>Depending on how the service we&rsquo;re trying to fix is written, you may have to set a trusted proxy using an IP or you may be able to set a domain name, or either. Don&rsquo;t desist if it doesn&rsquo;t work at first try.</li>
</ul>
</li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Changes to My Network, Sponsored by XMPP</title>
      <link>https://gvisoc.com/en/posts/changes-xmpp/</link>
      <pubDate>Sun, 31 Aug 2025 00:49:35 +0000</pubDate><author>gabriel@gvisoc.com (Gabriel Viso Carrera)</author>
      <guid>https://gvisoc.com/en/posts/changes-xmpp/</guid>
      <description>A few days ago, someone recommended Snikket to me as an easy-to-install and maintain XMPP server, and as a result, I&amp;#39;ve changed the way I expose my services to the internet.</description>
      <content:encoded><![CDATA[<p><strong>A few days ago, someone recommended <a href="https://snikket.org">Snikket</a> to me as a simple option for setting up an XMPP server<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>, and I jumped in</strong> head first. Halfway through the descent, I encountered a bunch of interesting problems to solve, which I will break down step by step in the next few posts.</p>
<p>In this one, I&rsquo;m going to tell you what those problems are, why they arise, why I want to solve them, and how I&rsquo;ve solved them, but from a design point of view for now. You won&rsquo;t find a set of commands to solve all this, but rather some context about what I&rsquo;m trying to solve; detailed instructions will come in the future.</p>
<p>Until now, all my services hosted &ldquo;underneath my printer&rdquo; were Web services, that is, based on communications through ports 80 and 443 (HTTP and HTTPs). These ports were enough to connect all my services with my devices or my browser, or with other servers in the case of my Mastodon instance. To serve so many services behind the same ports, I use <a href="https://nginx.org/">NGINX</a> as a <a href="https://en.wikipedia.org/wiki/Reverse_proxy">reverse proxy</a>:</p>
<figure class="ma0 w-75">
    <img loading="lazy" src="/images/20250901-before.png"
         alt="A diagram showing the connectivity between Cloudflare and a server called barcas, which contains NGINX and a series of services running on Docker"/> <figcaption>
            <p>&ldquo;Barcas&rdquo; is the name of my server.</p>
        </figcaption>
</figure>

<p>Unlike my other services, <strong>XMPP uses more TCP and UDP ports than 80 and 443</strong>: 5000, 5222, 5269, etc. In the case of Snikket, as many or all other alternatives might as well, ports 80 and 443 are also used to provide a web control panel and to manage users and chat rooms.</p>
<p>In principle, the easiest configuration for installing Snikket would be as follows:</p>
<ul>
<li>Configure ports 80 and 443 in NGINX to be able to use Snikket&rsquo;s user and conversation control panel.</li>
<li>Configure direct access to all other Snikket ports: 5222, 5269, etc. Here is the <a href="https://snikket.org/service/help/advanced/firewall/">complete list</a>. As they are not shared amongst other services, reverse-proxying them is not needed.</li>
<li>Configure the DNS for my XMPP service with my domain provider, which is <a href="https://www.cloudflare.com/">Cloudflare</a>.</li>
</ul>
<p>This configuration, which would look good at first glance, looks like the following:</p>
<figure class="ma0 w-75">
    <img loading="lazy" src="/images/20250902-snikketsimple.png"
         alt="A picture showing the connectivity between Cloudflare and a server called barcas, which contains NGINX and a series of services running on Docker."/> <figcaption>
            <p>This configuration, although it seems to make sense, does not work. Cloudflare only offers proxy functions for ports 80 and 443 in its free account, so traffic from the rest of Snikket&rsquo;s ports is discarded.</p>
        </figcaption>
</figure>

<p><strong>However, there is a problem that is not obvious, and the above configuration does not work</strong> out of the box. The Cloudflare security features that are available to free accounts in the form of “the Cloudflare proxy” only support ports 80 and 443. With the Cloudflare proxy enabled, all non-web traffic (80 and 443) was discarded, so none of the ports involved in XMPP conversations reached my server.</p>
<p>Disabling the Cloudflare proxy for the Snikket subdomain configuration makes everything work, and looks like this:</p>
<figure class="ma0 w-75">
    <img loading="lazy" src="/images/20250902-snikketactual.png"
         alt="A diagram showing the connectivity between Cloudflare and a server called barcas, which contains NGINX and a series of services running on Docker"/> <figcaption>
            <p>This configuration, although it works, poses a risk. By disabling the proxy function, my home IP address (where &ldquo;barcas&rdquo; is accessed) is exposed to the entire internet. A distributed denial-of-service attack affecting my internet service provider could leave the entire house without internet, which we don&rsquo;t like.</p>
        </figcaption>
</figure>

<p><strong>Disabling Cloudflare&rsquo;s proxy is not ideal because it exposes the server&rsquo;s IP address</strong> to the rest of the internet. My server is at home, so a DDoS attack that, by pure chance, included the IP address assigned to me by my internet service provider would bring down my entire home network. That would prevent us from working or studying from home and would probably cause problems with my service provider.</p>
<p>An alternative to this is to hire a cheap server from any cloud hosting provider (a <a href="https://en.wikipedia.org/wiki/Virtual_private_server">VPS</a>) and move all my NGINX configuration to it, connecting this cloud server to my home server via a VPN (the free <a href="https://tailscale.com">Tailscale</a> account works very well and is more than enough).</p>
<ul>
<li>The cloud hosting provider would protect my server because the exposed IP would be theirs, not my home&rsquo;s, and these types of companies protect their infrastructure from attacks, at least at the network level. Behind the XMPP ports there is no content, just a messaging service that also encrypts traffic, so there is no problem with losing the security and anti-bot layer that Cloudflare provides for these ports.</li>
<li>Connecting the VPS to my server via a VPN is perfect for this case because it hides the IP address of my home network, saving me from having to configure it in the DNS control panel and also from having to open ports on my router. In addition, for all traffic between the VPS and my server, a layer of encryption would be applied to protect it in case it was not already encrypted.</li>
</ul>
<figure class="ma0 w-75">
    <img loading="lazy" src="/images/20250902-snikketvps.png"
         alt="A diagram showing the connectivity between Cloudflare, a VPS containing NGINX, and between it and a server called barcas using Tailscale. Barcas contains a series of services running on Docker."/> <figcaption>
            <p>This configuration works and solves the problem of the previous configuration, but it requires finding a solution to redirect traffic from all ports other than 80 and 443 to ‘barcas’.</p>
        </figcaption>
</figure>

<p>Why move only the NGINX configuration and not all services? For reasons of resources and price: to run NGINX and a series of firewall rules, the requirements we need are minimal. A server with 1 GB of RAM and a CPU is more than enough, and that costs between 3 and 6€ per month. If I moved everything<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>, I would need between 8 and 16 GB of RAM, much more storage, CPU power, a backup solution, etc. All of this would cost me around 40€ or more per month. Since I have a server at home where I can run the services, moving everything to the VPS would be an unnecessary expense in my case.</p>
<p>So, it seems that hiring a simple VPS to have NGINX configured there and connecting it to the home server via Tailscale would solve my problems at a very low cost (3 to 6€ per month).</p>
<p>The only additional complication with this method is that we need to have some mechanism to redirect traffic from the XMPP ports (several ports such as 5222, 5269, etc. on TCP and UDP) from the server we hire to our server (at home or wherever). In the end, I solved it by configuring the VPS to do IP forwarding and then setting up a series of DNAT<sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup> rules on its firewall.</p>
<p>We will look at the configuration of all the elements in more detail in future posts.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p><em><a href="https://es.wikipedia.org/wiki/Extensible_Messaging_and_Presence_Protocol">Extensible Messaging and Presence Protocol</a></em> &ndash; XMPP is a protocol that connects communities of users through their servers, creating conversations between pairs of users or rooms for group conversations. Google Talks was the most popular example of an XMPP application. One advantage of XMPP is that it is a federated protocol, meaning that even if I connect to a server where I am the only user, I can establish contact with any other user of any other service that uses XMPP on the planet. Usernames in XMPP have the same format as an email address, and servers communicate with each other when their users need them to.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>Although in this article I talk almost exclusively about Snikket, I have many other services on my home server: Mastodon, Castopod, a few blogs, Linkwarden, FreshRSS, Actual Budget, Forgejo, and Nextcloud.&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p><em>Destination <a href="https://en.wikipedia.org/wiki/Network_address_translation">NAT</a></em>. Destination Network Address Translation.&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded>
    </item>
  </channel>
</rss>
