#

Security

Subscribe
Security October 29, 2026·9 min read

UFW Firewall Rules for SSH and Web Hosting

Before you enable UFW on a remote Ubuntu server, allow your SSH port and your admin IP if you restrict it, then open only the web ports you use. Once UFW is on, log in with a fresh SSH session and load the site from another network. UFW's rule list alone doesn't show what is reachable when Docker publishes ports or a provider firewall filters traffic upstream, so check those too.

Check the server and recovery path before changing UFW

UFW server preflight

Your VPS can already have active firewall rules, a custom SSH port, a reverse proxy, or a network filter upstream. Run these three commands to see your firewall state, listening TCP sockets, and installed application profiles:

sudo ufw status verbose
sudo ss -ltnp
sudo ufw app list

SSH uses 22/tcp by default, but your daemon can listen elsewhere, and the OpenSSH profile can describe a different port than your customized service.

  • Administration: Note the port SSH listens on and the network you connect from.
  • Website: Find the Nginx, Apache, or reverse-proxy listener, and whether it runs on the host or in a container.
  • Existing policy: List any required services besides SSH and the public site before changing defaults.
  • Reachability: Check whether public IPv6, a provider firewall, or Docker-published ports affect the route.
  • Recovery: Confirm you can reach a provider console or other out-of-band access if SSH stops working.
Linux listening sockets

Take an Apache VM that serves HTTP and HTTPS publicly but accepts SSH only from a private address range: the two services need different rules.

Allow the real SSH port before enabling UFW

UFW SSH allowance

Add an allow rule for the SSH port you found in the first step. Decide who can connect before writing it. An admin with a stable source address can use a narrower rule, and other setups can need a different access path.

Administration scenarioRule
SSH on TCP 22, open to the publicAllow 22/tcp
Custom SSH portAllow that exact TCP port, matching the daemon
Fixed administrator IPAllow only that source, while the address stays usable
VPN or private administrationAllow the private source, once the route is reliable

To allow the port from any source, replace SSH_PORT with your port number:

sudo ufw allow SSH_PORT/tcp

With SSH on port 2222, the rule looks like this:

sudo ufw allow 2222/tcp

If your admin address is stable and confirmed, use this instead:

sudo ufw allow from ADMIN_PUBLIC_IP to any port SSH_PORT proto tcp

For the address 203.0.113.10 and port 2222:

sudo ufw allow from 203.0.113.10 to any port 2222 proto tcp

A source-specific rule blocks you if you connect from a network it doesn't permit.

Restricted SSH sources

An r/linuxadmin poster running an internal wiki VM described the custom-port mistake: “Our ssh service was running on a custom port. Guess which port I had forgotten to allow.”

Set the default policies

If UFW is inactive, the usual starting point denies incoming connections and allows outgoing ones. Add your SSH rule and any other required services first, then set the defaults:

sudo ufw default deny incoming
sudo ufw default allow outgoing

If UFW is already active, rules take effect as you add them, so you only need to validate the live policy at the end. Changing a default policy on a live firewall can flush chains and interrupt connections, so schedule that change when you have working recovery access.

Hold ufw enable until the web rules are in place.

UFW's limit action can restrict bursts of new SSH connections.

Allow only the web ports your site uses

Web firewall protocols

For a conventional public site, allow TCP 80 for HTTP and TCP 443 for HTTPS. Your listener and certificate-validation method decide which of them you need.

ServicePort and protocolWhen to allow it
HTTP or HTTP redirect80/tcpSite accepts HTTP requests or redirects
HTTPS443/tcpTLS website listens on that port
Let's Encrypt HTTP-0180/tcpYou use that ACME challenge
Let's Encrypt TLS-ALPN-01443/tcpYou use that ACME challenge
HTTP/3 over QUIC443/udpYour server has a QUIC listener on UDP 443
Let's Encrypt DNS-01DNS record challengeNo inbound challenge port needed

The Let's Encrypt challenge-type documentation explains each validation path.

Open ports directly or use an app profile

For host services on the standard web ports, add direct rules:

sudo ufw allow 80/tcp
sudo ufw allow 443/tcp

You can also use an installed Nginx or Apache UFW profile. Check its declared ports first:

sudo ufw app info 'Nginx Full'

If the profile exists and matches your Nginx listeners, use it instead of the two direct rules:

sudo ufw allow 'Nginx Full'
Nginx profile rules

Apache users can do the same with the Apache Full profile if it's installed. A rule for 443/tcp lets packets through the host firewall, but Nginx, Apache, or your reverse proxy still has to listen and serve working TLS.

If you have an HTTP/3/QUIC listener on UDP 443, add that port as well, whether you used direct rules or a profile:

sudo ufw allow 443/udp

For a QUIC listener on another port, use that UDP port instead.

Keep MySQL, MariaDB, Redis, and administrative backend ports off the public web allowlist unless a specific, justified access path requires them.

Enable UFW and verify SSH and website access

External connectivity testing

Once your SSH and web rules are in, enable UFW if it's inactive. Keep your current SSH session open until a new login works, because enabling the firewall can interrupt a remote connection. Also check any cloud firewall that filters traffic before it reaches the host.

sudo ufw enable
sudo ufw status verbose
sudo ufw status numbered

Then work through the table.

CheckPassing resultIf it fails, look at
UFW statusExpected policies and allowances shownFirewall configuration
New SSH connectionAn outside client completes a new loginSSH listener or network filtering
Web listenerExpected address and TCP port have a listenerWeb server or reverse-proxy configuration
External HTTPA public request gets the page or redirect you expectInbound path or web service
External HTTPSA public request completes TLS and gets the right responseTLS service or network path
Unexpected public portsAn outside check finds no unapproved open services on the tested address and portsUnexpected listener, published container port, or upstream policy
Public IPv6, if usedIPv6 requests succeed on their ownIPv6 listener, UFW setting, or upstream IPv6 policy

An external port check shows only what that one client sees. A closed or filtered result doesn't tell you which firewall dropped the traffic, and the port can still be reachable from other networks.

A second SSH terminal isn't always a new TCP connection. OpenSSH connection multiplexing can reuse an existing master connection, so test with a client or configuration that opens a new one.

SSH connection verification

For the website, request the public hostname from another network and check the HTTP response or redirect and the HTTPS application response. A request against localhost tests something else. On a dual-stack host, check /etc/default/ufw and test public IPv4 and IPv6 separately where configured.

A Nextcloud admin running Ubuntu 22.04 behind a reverse proxy wrote, “I am trying to figure out why the heck I can't open port 443 on my Ubuntu 22.04 server.”

Account for Docker-published ports

Docker published exposure

Docker-published ports can stay reachable from outside despite UFW's incoming restrictions. Docker manages its own packet-filtering and NAT rules. Published traffic can be redirected to the container during NAT processing, before it reaches the host INPUT filtering path that ordinary UFW rules use.

See the Docker packet-filtering documentation for the details.

Inspect the host bindings of your running containers:

docker ps

Read the host-side address and published port. A binding to a public interface or all interfaces is exposed differently than an intentional loopback binding. Test a private service's published port from an outside network. If it's reachable and shouldn't be, investigate the container's published-port policy.

Docker binding inspection

A VPS operator in an r/selfhosted Docker firewall discussion wrote, “Same mistake again. Docker silently bypassing my firewall and exposing my database to the internet.”

Version-specific detail: Docker documents that releases before 28.0.0 had a localhost-publishing exception that let other hosts on the same Layer 2 network reach a port bound to localhost.

Put databases and caches that only other containers use on internal Docker networks. Adding a UFW deny rule blindly or changing Docker's daemon-level firewall handling won't suit every setup, so match the fix to your Docker configuration.

Know which firewall layer controls each connection

Cloud firewall controls

On a hosted VPS, the provider can filter traffic before it reaches Ubuntu. An AWS Security Group or DigitalOcean Cloud Firewall can deny a connection even when UFW has a matching allow rule.

Say UFW allows HTTPS on TCP 443, but the security group attached to your instance permits only SSH. Fix it with an inbound rule at the provider, because another host-side allow rule changes nothing.

Provider ingress policies

Check the protocol, port, destination, and allowed source in your provider's policy against what you intend.

Fix blocked SSH or an unreachable website

Firewall problem diagnosis

A timeout, a refused TCP connection, and an HTTP error are three different symptoms, and more than one cause can fit each.

SymptomLikely causeCheck next
Fresh SSH connection times outFiltering or routing before SSH is reachedSSH port and inbound network path
SSH connection refusedNo listener, or active rejectionSSH daemon and listening address
Website won't open a TCP connectionHost or provider filtering, or no listenerPublic web port and service state
HTTPS fails after connectingWeb server or TLS configurationHTTPS listener and application setup
HTTP 5xx responseProxy or application failureWeb-service logs and upstream component
Private container port is publicly reachablePublished host binding or container filteringDocker publication and network configuration

If you lose SSH access, use the provider console or other out-of-band access to inspect the actual listener and find the rule that blocks it. Fix the cause, then run the acceptance checks again.

Remote recovery access

Avoid a blanket ufw reset while troubleshooting, since it discards your other rules and can change production access.

Change or remove a rule

UFW numbered rules

List the rules with numbers to find the obsolete entry, then delete it by number or by matching rule definition:

sudo ufw status numbered
sudo ufw delete NUMBER
sudo ufw delete allow 8080/tcp

Rule numbers shift after a deletion, so refresh the listing before removing another entry. If you're narrowing SSH access, add the replacement SSH rule first.

NoScript Team