allow port 22 through the docker-user allowlist

the DOCKER-USER chain is the only thing gating docker-published ports from
the internet — docker writes its own DNAT/FORWARD rules and bypasses ufw, so
`ufw allow <port>` has no effect on a published container port. the allowlist
permitted only 80 and 443, so a machine provisioned from this template dropped
gitea ssh silently.

the failure is hard to spot: the port looks open locally and docker ps shows it
published, but external clients hang at TCP connect with no refusal. local tests
pass because they arrive via lo and match the loopback RETURN before reaching the
DROP. comments added so the next person recognises it faster.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-14 09:51:22 +00:00
co-authored by Claude Opus 5
parent 4d14e11f6c
commit e36c6bb431
@@ -2,8 +2,18 @@
# Append these to /etc/ufw/after.rules (after the existing COMMIT)
# Blocks all external access to Docker-published ports except:
# - Trusted IPs (add your own)
# - Explicitly allowed public ports (80, 443)
# - Explicitly allowed public ports (80, 443, 22)
# - Docker internal and loopback traffic
#
# This chain is the ONLY thing gating Docker-published ports from the internet:
# Docker writes its own DNAT/FORWARD rules and bypasses ufw entirely, so a
# `ufw allow <port>` has no effect on a published container port. If a container
# port must be reachable publicly, it needs a RETURN line below.
#
# Failure mode to recognise: the port appears open locally and `docker ps` shows
# it published, but external clients hang at TCP connect with no refusal. Local
# tests pass because they arrive via `lo` and match the loopback RETURN above,
# never reaching the DROP. Test from another machine, not from the host itself.
*filter
:DOCKER-USER - [0:0]
@@ -23,6 +33,9 @@
# Allow public ports
-A DOCKER-USER -i eth0 -p tcp --dport 80 -j RETURN
-A DOCKER-USER -i eth0 -p tcp --dport 443 -j RETURN
# gitea SSH (NPM stream -> gitea:22). Host sshd does not use :22 publicly on a
# box that serves gitea — it moves to :2022 plus the tailnet IP, see ssh.socket.
-A DOCKER-USER -i eth0 -p tcp --dport 22 -j RETURN
# Drop everything else from external
-A DOCKER-USER -i eth0 -j DROP