As part of building my self-hosted Hugo publishing workflow, I ended up with a surprisingly useful reminder:
Even if your deployment looks simple, it often relies on more than one network flow.
This came up when I started tightening firewall rules around my DMZ web server.
The Setup (High Level)
My environment looks roughly like this:
- A trusted workstation where I write content
- An internal Git server (Gitea) on the LAN
- A public-facing web server in a DMZ
- A deployment script that pulls updates and rebuilds the Hugo site
The publishing workflow is:
- Edit content locally
- Commit + push to Git
- Trigger a deploy command remotely
- The web server pulls the latest content and rebuilds the site
The Problem: SSH Worked… Until It Didn’t
Once everything was running smoothly, I decided to lock down SSH access to the DMZ host.
So I created a firewall rule allowing:
Workstation → DMZ Web Server on port 22
That seemed correct.
But suddenly my deploy script (hillpush) failed.
As a test, I temporarily opened all ports — and everything worked again.
That told me immediately:
Another network dependency was being blocked.
The Key Realisation: Deployment Requires Two SSH Flows
Even though I was only SSH’ing into the web server, the web server itself also needed outbound access.
There are actually two separate SSH connections involved.
1. Workstation → Web Server (Remote Trigger)
This is the obvious one:
ssh user@webserver "run-deploy-script.sh"
Firewall requirement:
Allow SSH from the trusted workstation to the DMZ host.
2. Web Server → Git Server (Pull Latest Content)
Inside the deploy script, the DMZ server runs something like:
git fetch origin
git reset --hard origin/main
hugo --minify
That means the DMZ host must also be able to reach the Git server over SSH.
Firewall requirement:
Allow SSH from the DMZ host to the internal Git server.
The Fix: Minimal Rules, Maximum Security
Once I understood the full workflow, the firewall rules became very clean.
Rule A — Trusted Workstation → DMZ Web Server
Allow port 22 only from a trusted source.
Example:
Source: Trusted clients network
Destination: Web server
Port: 22
Rule B — DMZ Web Server → Internal Git Server
Allow outbound SSH only for Git pulls.
Example:
Source: Web server
Destination: Git server
Port: 22
Quick Connectivity Test
A useful test from the DMZ host is:
nc -zv git-server 22
If that fails, your deploy script will not be able to fetch updates.
Final Result
With both rules in place, I ended up with:
- SSH access locked down to a single trusted machine
- No unnecessary lateral movement between VLANs
- A working automated deployment pipeline
- Only the minimum required ports open
Takeaway
When hardening infrastructure, always map the full flow:
- Who connects to the server?
- What does the server need to connect to?
In my case:
Deployment wasn’t just “SSH in” — it was also “Git pull out”.
A small detail, but a very real one.