2026年11月12日

2026年11月12日

Firewall: Allow Only Specific IPs with ufw, firewalld or nftables

This article is a localized edition of the Japanese original. Read the Japanese original

To make SSH, an admin screen or a database port "reachable only from my own IP address," set up the firewall in this order: first add a "rule that allows a specific source IP," then "deny everything else." If you reverse the order, even your own connection is cut off while you work. This article covers preparing to avoid locking yourself out, how to write the rules in ufw, firewalld and nftables, how cloud security groups and Docker fit in, and how to verify the result.

Decide First: What to Allow, and for Whom

IP restriction is the job of deciding "which ports" to open "to which sources." Ports 80/443, which serve your public website, are normally left open to everyone because ordinary visitors connect to them. Typical candidates for limiting to specific IPs are:

  • SSH (port 22, or the port you changed it to)
  • Databases (such as port 3306 for MySQL/MariaDB; often there is no need to open them externally at all)
  • Ports for monitoring agents and admin tools
  • An entire staging website

If you want to restrict only the WordPress login screen (wp-login.php) or the admin area, a firewall, which works per port, is not the right tool; restrict by URL in the web server (Nginx or Apache) configuration instead. Restricting port 443 in the firewall would make the whole site invisible.

For the IP addresses you allow, check whether your home or office connection has a "static IP." Typical residential connections can get a new IP address when they reconnect. If you lock things down tightly on a connection whose IP changes, one day you will suddenly be unable to get into SSH. In that case, a realistic approach is to go through a bastion server with a static IP or a VPN (WireGuard, etc.) and allow only the bastion's or VPN's address.

Preparing to Avoid Locking Yourself Out

When you work on the firewall of a remote server, a mistake can cut your SSH connection and leave you unable to undo it. Make these preparations before you start:

  • Find out how to use the "console connection" available from your VPS or cloud control panel (such as a browser-based serial console)
  • Save the current rules (write the output of the commands described below to a file)
  • Open two SSH sessions: make changes in one and check in the other that the connection stays up
  • Check your current global IP from the server side

While connected over SSH, you can check your source IP as seen from the server like this:

# IP address of the SSH client (the left one is your source)
echo "$SSH_CLIENT"
who -m

You can also look at an "IP checker site" in a browser, but if you go through a corporate proxy or VPN, the address may differ from the one that reaches the server. Use the address seen from the server for your allow rules.

Allow Only Specific IPs with ufw (Ubuntu, Debian)

ufw evaluates rules from top to bottom and applies the first rule that matches. That means the rule for "the IP you want to allow" must be above any broader deny rule.

# 1. Check the current status (shows inactive if disabled)
sudo ufw status verbose

# 2. Default policy: deny incoming, allow outgoing
sudo ufw default deny incoming
sudo ufw default allow outgoing

# 3. Allow SSH only from your IP (example: 203.0.113.10)
sudo ufw allow from 203.0.113.10 to any port 22 proto tcp comment 'admin ssh'

# 4. Allow the public website for everyone
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp

# 5. Review the rules, then enable
sudo ufw show added
sudo ufw enable

If there is already a rule that allows 22/tcp for everyone (such as the OpenSSH application profile), adding an IP-limited rule on its own achieves nothing. Confirm that you can connect with the new rule, then delete the allow-all rule.

sudo ufw status numbered
# Check the number, then delete (example: rule 3)
sudo ufw delete 3

To allow a whole network, you can use CIDR notation such as 203.0.113.0/24. If you connect over IPv6, you need to allow your IPv6 address in the same way. IPv6 support in ufw is enabled by IPV6=yes in /etc/default/ufw (the default in many environments).

Allow Only Specific IPs with firewalld (RHEL, AlmaLinux, Rocky Linux, etc.)

firewalld manages which services are allowed for which sources or interfaces in units called "zones." A clean setup is to create an admin zone, register the allowed IP and SSH in it, and then remove SSH from the public zone.

# Check the current configuration
sudo firewall-cmd --get-active-zones
sudo firewall-cmd --zone=public --list-all

# Create an admin zone and register the allowed IP and SSH
sudo firewall-cmd --permanent --new-zone=admin
sudo firewall-cmd --permanent --zone=admin --add-source=203.0.113.10/32
sudo firewall-cmd --permanent --zone=admin --add-service=ssh
sudo firewall-cmd --reload

# After confirming in another session that SSH works via the admin zone,
# remove the SSH allowance from the public zone
sudo firewall-cmd --permanent --zone=public --remove-service=ssh
sudo firewall-cmd --reload

Traffic from IPs registered as sources in the admin zone is processed by the admin zone's rules, and everything else by the public zone's rules. In current firewalld, the basic behavior is that traffic not allowed in the admin zone is rejected rather than passed on to the public zone ("zone drifting," which existed in older versions, is disabled). So if you also view your own site (80/443) from the admin IP, add --add-service=http and --add-service=https to the admin zone as well. Rules with --permanent take effect after a reload; rules without it go only into the runtime configuration and disappear on restart.

If you want to try a rule and have it removed automatically if something goes wrong, you can omit --permanent and specify --timeout.

# Rule valid for only 5 minutes (runtime configuration only)
sudo firewall-cmd --zone=admin --add-service=ssh --timeout=300

Putting the source IP in the trusted zone is also often suggested, but trusted is a zone that allows all traffic. If you want to allow only SSH, using a zone that has only the services you need, as above, better matches your intent.

Writing nftables Rules Directly

If you manage nftables rules directly instead of using ufw or firewalld, grouping the allowed IPs into a "set" makes them easier to manage.

#!/usr/sbin/nft -f
# Example /etc/nftables.conf (it replaces the existing rules, so always review it before applying)
flush ruleset

table inet filter {
    set admin_v4 {
        type ipv4_addr
        flags interval
        elements = { 203.0.113.10, 198.51.100.0/24 }
    }

    chain input {
        type filter hook input priority 0; policy drop;

        ct state established,related accept
        ct state invalid drop
        iif "lo" accept
        meta l4proto { icmp, ipv6-icmp } accept

        ip saddr @admin_v4 tcp dport 22 accept
        tcp dport { 80, 443 } accept
    }
}
# Syntax check (does not apply)
sudo nft -c -f /etc/nftables.conf
# Apply
sudo nft -f /etc/nftables.conf

If you forget ct state established,related accept, which allows existing connections, replies to your current SSH session may stop getting through the moment you apply the rules. Blocking ICMP (especially ICMPv6 for IPv6) makes IPv6 connectivity itself unstable, so normally allow it. If you rewrite nftables directly in an environment where ufw or firewalld is running, its rules mix with those of the management tool and become confusing, so stick to one of them.

How Cloud Security Groups and Docker Fit In

Cloud and VPS packet filters

AWS security groups and the packet filters in VPS providers' control panels stop traffic before it reaches the OS firewall. Restricting in only one of them works, but applying the same policy on both the OS side and the cloud side leaves you protected even if one is misconfigured. When you cannot connect, checking both sets of rules is the basic way to isolate the problem.

Ports published by Docker

For ports published with Docker, such as -p 8080:80, Docker adds iptables (or nftables) rules directly, so they may be opened to the outside without passing through your ufw rules. When a container port is reachable from outside even though you think ufw is blocking it, this is often the cause. Countermeasures include publishing the container only locally, as in 127.0.0.1:8080:80, or adding restriction rules to the DOCKER-USER chain described in the Docker documentation. How Docker integrates with the firewall may change between Docker versions (as of October 2026), so check the documentation for the version you use.

When a CDN sits in front

If traffic goes through a CDN such as Cloudflare, the source of the traffic reaching your web server is the CDN's IP addresses. You can restrict ports 80/443 on the origin server to connections from the CDN only, but the CDN's IP ranges can change, so you need a mechanism that regularly imports the officially published list.

Verifying After Setup

Once you are done, verify from both sides that you can connect from the allowed IP and cannot connect from anywhere else.

# Check the rules on the server
sudo ufw status verbose                     # ufw
sudo firewall-cmd --zone=admin --list-all   # firewalld
sudo nft list ruleset                       # nftables

# From another connection that is not allowed (smartphone tethering, etc.)
nc -vz -w 3 203.0.113.200 22   # 203.0.113.200 is an example server IP

If the attempt from the unallowed connection times out, the restriction is working. Finally, with a console connection ready, it is reassuring to check once that the rules survive a server reboot.

Summary

  • Add the "allow rule for the IP" first, confirm that you can connect, and only then "deny everything else" or delete the allow-all rule
  • Check how to use the console connection before you start and keep two SSH sessions open to avoid locking yourself out
  • ufw evaluates rules top to bottom, firewalld uses zones per source, and nftables is easier to manage with sets
  • On connections whose IP changes, allowing the address of a bastion server or VPN is the realistic approach
  • Also check cloud security groups, ports published by Docker, and source IPs when traffic comes through a CDN

Questions about WordPress? The publisher of this site is based in Japan and replies in Japanese. Contact (Japanese)

お気軽にご相談ください

お見積りへ お問い合わせへ
▲