Security

Restrict outgoing traffic with UFW egress rules

Switch UFW to deny outgoing traffic by default, then allow only DNS, NTP, HTTPS and package mirrors. Block outbound SMTP and avoid breaking system updates along the way.

AdvancedLinux server5 min read

Most firewalls only filter incoming traffic. Filtering outgoing traffic as well limits the damage if a website or container is ever compromised: malware cannot easily send spam, download tools or call home on odd ports. This guide moves UFW to a default-deny policy for outbound traffic, allows only what a typical server needs and shows how to test that updates still work.

Before you start

  • A Linux VPS running Ubuntu 22.04, Ubuntu 24.04 or Debian 12 with UFW active.
  • A sudo user connected over SSH, with SSH allowed in UFW. Check with sudo ufw status.
  • A list of what your server talks to: package mirrors, APIs, a backup target, a mail relay, a monitoring service. Anything you forget will break.

Warning: Egress filtering breaks things quietly: updates, certificate renewals, backups and webhooks fail without an obvious firewall message. Make sure incoming SSH is allowed before you start, add the outbound allow rules before you change the default policy, and keep the browser console in your VPS provider's control panel ready in case you need to undo it.

What stays working automatically

UFW is stateful. Replies to connections that came in are allowed out without any extra rule. So your SSH session and visitors to your website keep working after you deny outgoing traffic.

UFW also allows all traffic on the loopback interface. Services that talk to 127.0.0.1, including the local DNS stub 127.0.0.53 used by systemd-resolved on Ubuntu, are not affected.

What does stop is any connection the server starts itself: apt downloads, DNS lookups to upstream resolvers, NTP time sync, API calls and outgoing mail.

Find what the server needs

Before you lock anything down, look at the outbound connections the server already makes:

Bash
sudo ss -tunp state established

Note the remote ports and processes. Repeat a few times across a day, or turn on medium logging for a short period. You will typically see 53 (DNS), 123 (NTP), 80 and 443 (updates and APIs), and maybe 587 or 465 (mail relay) or 22 (backups over SSH).

Add the outbound allow rules

Add these first, while outgoing is still allowed:

Bash
sudo ufw allow out 53 comment 'DNS'
sudo ufw allow out 123/udp comment 'NTP'
sudo ufw allow out 80/tcp comment 'HTTP for package mirrors'
sudo ufw allow out 443/tcp comment 'HTTPS'

Notes on each:

  • DNS (53): allowed for both UDP and TCP on purpose. Large DNS answers fall back to TCP.
  • NTP (123/udp): keeps the clock right, which TLS certificates and logs depend on.
  • HTTP (80/tcp): Ubuntu and Debian mirrors usually serve packages over plain HTTP and check them with signatures. Removing this breaks apt update unless you switch mirrors to HTTPS.
  • HTTPS (443/tcp): APIs, Let's Encrypt, Docker Hub, Git hosting and most modern services.

Add what else your list showed, for example:

Bash
sudo ufw allow out 587/tcp comment 'SMTP submission to mail relay'
sudo ufw allow out to 198.51.100.20 port 22 proto tcp comment 'Backups over SSH'

Limiting a rule to one destination, as in the second example, is the tightest option for fixed targets.

Block outbound SMTP

Port 25 is how compromised servers send spam. Unless this server is a mail server, deny it explicitly so the intent is clear even if someone later loosens the default:

Bash
sudo ufw deny out 25/tcp comment 'No direct SMTP'

Applications that must send mail should use an authenticated relay on port 587 or 465 instead. See Port 25 blocked on a VPS: sending email for options.

Switch the default to deny

With the allow rules in place, change the outbound policy:

Bash
sudo ufw default deny outgoing
sudo ufw status verbose

The header should now read:

Default: deny (incoming), deny (outgoing), disabled (routed)

Your current SSH session stays open because its traffic is a reply to an incoming connection.

Tip: Run the next section's checks straight away. If something critical fails and you cannot fix it quickly, sudo ufw default allow outgoing restores the old behaviour in one command.

Check it works

Test each service you allowed:

Bash
sudo apt update
dig +short example.com
curl -sI https://example.com | head -1
timedatectl status | grep synchronized

You want a clean apt update, an IP address from dig, an HTTP status line from curl and System clock synchronized: yes.

Then confirm that blocked traffic really is blocked:

Bash
nc -vz -w 5 smtp.gmail.com 25

This should time out. With logging on, blocked attempts appear in the journal with OUT=eth0 set:

Bash
sudo journalctl -k --since "10 min ago" | grep 'UFW BLOCK' | grep 'OUT=e'

Use those lines (DST= and DPT=) to spot anything legitimate you missed.

Troubleshooting

  • apt update hangs on "Connecting to": port 80 or 443 is not allowed out, or a mirror uses another port. Check the blocked lines in the journal for the DPT.
  • Name resolution fails: DNS is not allowed, or only UDP is. Use sudo ufw allow out 53 without a protocol.
  • Let's Encrypt renewals fail: the ACME client needs outbound 443. Check sudo certbot renew --dry-run.
  • Docker containers still reach everything: container traffic is forwarded, not output from the host, so these rules do not cover it. Use the DOCKER-USER chain; see Stop Docker from bypassing your UFW firewall.
  • Lost access entirely: log in with your VPS provider's browser console and run sudo ufw default allow outgoing.

Next steps

Was this guide helpful?

Still stuck?

Ask MdmHost AI for a quick answer, or open a ticket and our team will take a look. We are here 24/7.