Every server connected to the internet is under constant, automated attack. Bots scan millions of IP addresses a day, probing for open ports, default credentials and known vulnerabilities — not because your server was targeted, but because it exists. A firewall is the first control that decides what any of that traffic can even reach. This guide covers UFW (Uncomplicated Firewall), the practical way to configure one on a Linux server, and the threat model it actually defends against. It also covers the parts that keep a firewall honest over time — restricting ports to known source addresses, managing and auditing rules without locking yourself out, handling IPv6, and confirming from the outside that your intended configuration and your real exposure still match.
For the server owner (plain English)
Think of your server as a building with hundreds of doors — each one a port, a numbered channel a specific service listens on. Without a firewall, every door is unlocked by default: SSH, your database, an admin panel, anything a piece of software happens to bind to.
A firewall's job is simple — lock every door, then hand out keys only to the doors that genuinely need to be open to the public (usually just HTTP/HTTPS and SSH). You do not need to read iptables syntax to know this matters: if you did not deliberately decide a port should be open, it should be closed.
Which server-hardening topic do you need?
This guide is the hub for locking down what a Linux server exposes to the internet. Jump straight to the piece you need:
| If you want to… | Start here |
|---|---|
| Control which ports are reachable from the internet | This guide — UFW default-deny setup (below) |
| Lock down SSH — disable root & password login, use keys | SSH security guide |
| Stop brute-force against a web login form | Login rate-limit guide |
| Find files you've left public (.git, .env, backups) | Website security guide |
What is UFW?
UFW stands for Uncomplicated Firewall. It is a friendly frontend for iptables (and, on newer systems, nftables) — the packet-filtering framework built into the Linux kernel via the netfilter project. iptables is powerful but its rule syntax is dense and easy to get wrong under pressure; UFW lets you describe intent in plain commands — “allow port 443”, “deny incoming by default” — and translates it into the correct rules for you.
UFW ships as the default firewall-configuration tool on Ubuntu and is a standard package on Debian and most derivatives (Ubuntu's UFW documentation). Every packet that reaches the server is checked against your rules before the kernel decides whether to let it through.
How does a firewall actually work?
A firewall inspects each incoming and outgoing packet and allows or blocks it based on rules you define. Every connection targets a port — a number from 0–65535 that identifies which service should handle it. Without a firewall, every port a process happens to bind to is reachable from anywhere on the internet.
Internet -> your server (port 22) -> SSH
Internet -> your server (port 80) -> HTTP
Internet -> your server (port 443) -> HTTPS
Internet -> your server (port 5432) -> database (should be CLOSED)The governing principle: deny everything by default, allow only what is necessary. This is the same least-privilege logic that governs good access control everywhere else — the firewall just applies it at the network layer.
How do you set up UFW safely?
1. Set default policies
Start by deciding what happens to traffic that matches no specific rule:
sudo ufw default deny incoming # block all inbound traffic by default
sudo ufw default allow outgoing # allow all outbound trafficThis lets your server reach the internet (package updates, API calls, pulling container images) while nothing from the internet can reach it unless you explicitly allow it.
2. Open only what you need
sudo ufw allow ssh # port 22 — remote access
sudo ufw allow 80/tcp # HTTP
sudo ufw allow 443/tcp # HTTPS
sudo ufw allow 443/udp # HTTP/3 (QUIC)3. Enable UFW — after the rules, never before
sudo ufw allow ssh # do this FIRST
sudo ufw enable # then this4. Verify the configuration
sudo ufw status verboseThis lists every active rule and the current default policies — confirm it matches what you intended before moving on.
What does the real threat look like?
Automated scanning is constant, not targeted
Every public IP address on the internet gets probed within minutes of coming online. These are not attackers who noticed your server specifically — they are bots that continuously scan the entire IPv4 address space for anything answering on a known-risky port:
Port 22 -> SSH brute force (common passwords)
Port 3306 -> MySQL with no password
Port 5432 -> PostgreSQL exposed to the internet
Port 6379 -> Redis with no authentication
Port 27017 -> MongoDB open to the worldIf a port like this is open and the service behind it is misconfigured, bots typically find it within hours — often less. Beyond ports, the same bots probe web servers for well-known exposed paths:
/.env— environment files with API keys and passwords/.git/config— an exposed git repository/wp-admin,/phpmyadmin— admin and DB-management panels/backup.zip,/debug— stray backups and debug panels left in production
A single exposed .env file can leak database credentials and API keys that hand an attacker full access to the application and everything it talks to.
SSH brute force is relentless and distributed
SSH is a near-permanent target. Bots cycle through common usernames (root, admin, ubuntu, deploy) and password lists, trying thousands of combinations an hour from thousands of different source IPs. Because the attack is distributed, blocking one IP does not stop it — the next guess simply comes from a different address.
A firewall alone is not sufficient defence for SSH; pair it with key-based authentication (disable password auth entirely) and an intrusion tool like Fail2ban that bans an IP after repeated failures. For the exact configuration — disabling root login, switching to SSH keys, choosing a safe username — see the SSH security guide.
How do you rate-limit SSH with UFW?
UFW has a built-in limit rule that throttles connections from any single IP that opens too many in a short window — a lightweight brake on brute-force probing without a separate tool:
sudo ufw limit ssh # deny an IP after ~6 connections in 30 seconds
sudo ufw limit 22/tcp # same, if SSH runs on the default portThis is a per-IP throttle, not a substitute for key-based auth. Because SSH brute force is distributed across thousands of addresses, rate limiting slows each individual source but can't stop the aggregate — it is a complement to disabling password login, never a replacement. Keep the real defence (keys only, no root login) as the primary control.
How do you read UFW logs?
Turn logging on so blocked traffic leaves a trail you can inspect when something looks off:
sudo ufw logging on # default 'low' level
sudo ufw logging medium # more detail if you're investigating
# blocked packets land in:
sudo tail -f /var/log/ufw.logA steady stream of [UFW BLOCK] entries against closed ports is normal background internet noise — that is the firewall doing its job. What's worth attention is blocked traffic to a port you thought was closed but a service is still binding, or allowed traffic to a port you don't recognise. Both are signs your intended configuration and reality have drifted apart.
How do you allow a port only from specific IPs?
Not every open port needs to face the whole internet. UFW can scope a rule to a single source address or subnet, which is the right move for SSH and any admin interface:
# SSH only from your office / VPN address
sudo ufw allow from 203.0.113.7 to any port 22 proto tcp
# ...or a whole trusted subnet
sudo ufw allow from 10.8.0.0/24 to any port 22 proto tcpOnce a scoped rule exists, remove any broad allow ssh rule so port 22 isn't still open to everyone. Source-restricting SSH is one of the highest-value firewall changes you can make: it takes the single most-attacked port off the public internet entirely, so the constant brute-force noise never even reaches the SSH daemon. The trade-off is operational — if your address changes (home ISP, travelling), you need console access or a VPN to get back in, so always keep a reliable second path before you lock a port to one address.
How do you change or remove a rule?
Rules are easiest to manage by number — list them, then delete by index:
sudo ufw status numbered # each rule gets a [n]
sudo ufw delete 3 # remove rule number 3
sudo ufw delete allow 80/tcp # ...or delete by the original specIf a configuration gets tangled, sudo ufw reset clears every rule and returns UFW to its defaults. That is useful for starting clean, but it also disables the firewall and drops your SSH allow rule, so treat it with the same caution as the very first enable: re-add the SSH rule before you re-enable. Editing rules on a live remote server always carries lock-out risk — make deletion and source-restriction changes over a session you can afford to lose, with a second way in ready.
Does UFW also cover IPv6?
Yes, if it's switched on. UFW manages IPv6 rules when IPV6=yes is set in /etc/default/ufw — the default on current Ubuntu and Debian. With it enabled, a rule like ufw allow 443/tcp applies to both IPv4 and IPv6 automatically, so you don't write rules twice. This matters more than it looks: a server can be fully reachable over IPv6 even when you've only reasoned about its IPv4 address. If IPv6 is disabled in UFW but the host still has a public v6 address, a service can be wide open over v6 while your v4 rules imply it's closed — so confirm both families are covered, and check what is actually listening on each with sudo ss -tlnp.
How do you use application profiles?
Many packages register a UFW profile so you can allow a service by name instead of memorising port numbers:
sudo ufw app list # see registered profiles
sudo ufw allow 'Nginx Full' # opens 80 and 443 together
sudo ufw app info 'Nginx Full' # check exactly what a profile opensProfiles are convenient and readable, but they are only as correct as the package that shipped them — 'Nginx Full' opens both HTTP and HTTPS, which is more than you want if you only serve HTTPS and redirect port 80. Run ufw app info before trusting a profile, and prefer explicit port rules whenever you want to be certain exactly what a single line opens.
Which firewall concepts actually matter?
Inbound vs. outbound traffic
Inbound: traffic someone else initiates toward your server. Deny this by default — you don't know who is connecting or why. Outbound: traffic your server initiates (updates, API calls, pulling images). Generally allow this by default so the server keeps functioning normally.
IP banning and NAT
Blocking an IP blocks everyone behind it, not just the attacker. Because of NAT (Network Address Translation), one public IP can represent hundreds or thousands of users sharing a connection — a corporate office, a university, a mobile carrier. Blocking individual IPs is reasonable for SSH, where legitimate users connect from a small set of known addresses; for general web traffic, rate limiting (capping requests per minute per IP) is the better fit, since it degrades abuse without collaterally blocking everyone on that connection.
The principle of least privilege
Open only what is actively needed. A database only your application talks to, on the same server, should never be reachable from the internet. An admin panel your team uses should not be publicly exposed. Every open port is attack surface — minimizing that surface is the whole point of the exercise.
Layered defense
A firewall is one layer, not the whole solution. A properly secured server stacks several:
- Firewall rules limiting access by port
- Strong authentication — SSH keys, not passwords
- Automatic blocking of repeated failed attempts (Fail2ban)
- Security updates applied automatically
- Monitoring and logging to catch unusual activity
Each layer compensates for the others' weaknesses — if one is bypassed, the rest still hold.
What are the most common UFW mistakes?
- Enabling UFW before adding an SSH rule. Locks you out immediately. Always run
sudo ufw allow sshbeforesudo ufw enable. - Assuming the firewall alone is enough. It controls which ports are reachable — it does not fix weak passwords, unpatched software, or a misconfigured application. It is a necessary foundation, not a complete answer.
- Opening ports “just in case.” Every open port is a potential entry point. Only open what is actively required, right now.
- Forgetting that some tools bypass UFW. Docker, notably, writes to
iptablesdirectly and can expose a container's port even when your UFW rules imply it should be blocked. Always confirm actual exposure rather than trusting the UFW rule list alone.
How do you verify your actual exposure?
Configuration intent and actual exposure can drift — a container publishes a port UFW didn't anticipate, a service you forgot about is still listening, a rule got removed during troubleshooting and never restored. Check both the server's own view and the internet's view of it.
From the server itself:
# see what's actually listening, and on which interface
sudo ss -tlnp
# review the active UFW rules
sudo ufw status verbosess -tlnp is the source of truth for what a process has bound to — cross-check it against your UFW rules and reconcile any mismatch. Then confirm from the outside: what actually answers when a stranger on the internet connects.
A UFW rule list is the server's intent; it can quietly go stale when a container or forgotten service binds a port anyway. A full external scan checks the internet's view — which ports and services actually answer from outside, the way an attacker's tooling sees them.
Scan your server's external attack surface →What's the takeaway?
UFW makes Linux firewall configuration accessible without giving up the power of iptables underneath. The core idea is simple: block everything by default, then explicitly allow only what the server needs to do its job. The threat is real and constant — automated bots probe every public IP continuously for open ports, default credentials and exposed files. A properly configured firewall will not stop every attack, but it dramatically shrinks what an attacker can even reach, and combined with key-based auth, automatic updates and monitoring, it forms the foundation of a server that can actually be defended. Get the default-deny policy right, open only what the job requires, restrict sensitive ports to known sources, and periodically confirm from the outside that nothing has drifted — that is the whole discipline, and it is well within reach of anyone running a single server.