common: ufw firewall¶
Problem¶
A freshly installed Debian has no active firewall - any service that later opens a port
(Docker containers included - see the docker role, "Docker bypasses ufw") is
immediately visible across the whole reachable network. This role sets up ufw with
default-deny for incoming traffic and opens only the ports actually needed.
Variables¶
All variables are prefixed common_ufw_* and have sensible defaults in
ansible/roles/common/defaults/main.yml.
| Variable | Default | Meaning |
|---|---|---|
common_ufw_enabled |
true |
Enables ufw at the end of the role run |
common_ufw_default_incoming |
"deny" |
Default policy for incoming traffic |
common_ufw_default_outgoing |
"allow" |
Default policy for outgoing traffic |
common_ufw_allow_ssh |
true |
Opens common_ssh_port/tcp automatically. Only set to false if SSH access is secured through some other mechanism |
common_ufw_extra_rules |
[] |
List of further rules, e.g. - {port: 443, proto: tcp, comment: "HTTPS"} |
What gets changed¶
- The
ufwpackage is installed. - Default policies (
ufw default deny incoming/allow outgoing). - Allow rule for
common_ssh_port/tcp, then one for each entry incommon_ufw_extra_rules. ufw enableruns as the last step - only once the SSH rule is in place.
Prerequisite: collection¶
Uses community.general.ufw, not ansible.builtin. Before the first run:
ansible-galaxy collection install -r ansible/requirements.yml
Verification¶
sudo ufw status verbose
Expected output (with default values): status active, Default: deny (incoming), allow
(outgoing), a rule for port 22/tcp (or the configured common_ssh_port).
Also check from a second machine: a connection to a port that isn't opened must hang or time out, not return "connection refused" (that would mean a closed but unfiltered port - a sign that ufw isn't actually sitting in front of the service as expected).
Pitfalls¶
- Order: only enable ufw after the SSH rule is in place - otherwise the
default-deny cuts the running Ansible connection. The role preserves this order
(
tasks/ufw.yml); a hand-rolled playbook has to enforce it itself. - Docker bypasses ufw: a container port published via
ports:is reachable on the LAN despite active ufw rules (Docker's owniptablesrules sit ahead of ufw's rules in the chain). This role doesn't change that - container ports belong bound to127.0.0.1, with public access going through a reverse proxy or Tailscale. - Changing the SSH port: if
common_ssh_portchanges, this role automatically opens the new port along with it - still apply both roles (SSH and ufw) in the same run, never change the port manually insshd_configand run ufw separately or later.