How to allow SSH from a specific IP only (without lockouts)
An SSH port open to the whole internet gets password guesses all day. The simplest fix is to allow SSH from a specific IP only, so the server answers your address and nobody else's. This guide does that with ufw on Ubunt
An SSH port open to the whole internet gets password guesses all day. The simplest fix is to allow SSH from a specific IP only, so the server answers your address and nobody else's. This guide does that with ufw on Ubuntu, checks it the right way, and adds a timer that reopens the door if you get it wrong. You also get a second lock inside SSH itself, and a plan for the day your home IP changes.
Try it on Hubfly space โ: run a Linux VM with root access and a firewall in front of it.
Versions used: Ubuntu 24.04, ufw 0.36.2, OpenSSH 9.6p1. You need an SSH session as a user with sudo (deploy below; use your own user name).
Step 1: Find the IP you connect from
Don't guess it from a "what is my IP" site on another network. Ask the server, from the SSH session you already have:
echo "$SSH_CLIENT" | cut -d' ' -f1
203.0.113.24
That's the address the server sees, which is the one the rule has to match. If it's an IPv6 address, use that address in the rules below instead.
If you connect from more than one place (home, office, a VPN), write all of them down now.
On a Hubfly space Box or any server behind a port mapping, check that this address really is yours. If it isn't, the server can't tell visitors apart by IP, so set the allowlist in the provider's firewall instead (see the Box section below) and skip Step 7.
Step 2: Look at the rules you already have
Skip this step and the allowlist can do nothing at all.
sudo ufw status numbered
If it only says Status: inactive, the firewall is off. Run sudo ufw show added to list the rules that will load when you turn it on, and skip the default-policy check below (Step 4 sets it).
If you see a line like this, an old rule already lets everyone in:
[ 1] 22/tcp ALLOW IN 203.0.113.24
[ 2] 22/tcp ALLOW IN Anywhere
ufw checks rules from the top and stops at the first match. Any connection to port 22 matches rule 2, so your new IP rule changes nothing. A setup script or an earlier sudo ufw allow ssh often leaves this behind (it may be listed as OpenSSH instead of 22/tcp). You'll delete it in Step 5, after the new rule is in place.
Also check the default policy. It has to be deny for incoming traffic:
sudo ufw status verbose | grep Default
Default: deny (incoming), allow (outgoing), disabled (routed)
Step 3: Set a safety net before you change anything
If you make a mistake, the firewall should switch itself off after 5 minutes. Cache your sudo password first, so the background job doesn't sit waiting for it:
sudo -v
nohup sudo sh -c 'sleep 300; ufw disable' >/dev/null 2>&1 &
Keep the nohup. Without it, a background job started from an interactive SSH session is killed when you log out, which is exactly when you'd need it.
To cancel the timer once everything works, kill the shell that runs it, not the sleep:
sudo pkill -f '^sh -c sleep 300'
The terminal prints Terminated: that's the timer stopping. Don't kill only the sleep. That ends the wait early, and the shell then runs ufw disable right away.
Step 4: Allow SSH from a specific IP only
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow from 203.0.113.24 to any port 22 proto tcp
Add one allow from line per address from Step 1. For a range, use CIDR notation, and check how big it is: 10.0.0.0/30 sounds like one address but covers four (.0 to .3), so a neighbour at .3 gets in too.
If SSH listens on another port, use that number. A rule for 22 won't help you on 2222.
If the firewall isn't on yet, turn it on:
sudo ufw enable
Command may disrupt existing ssh connections. Proceed with operation (y|n)? y
Firewall is active and enabled on system startup
Step 5: Remove the rule that lets everyone in
Delete the old open rule from Step 2 by its text (use sudo ufw delete allow OpenSSH if it's listed that way):
sudo ufw delete allow 22/tcp
sudo ufw status numbered
[ 1] 22/tcp ALLOW IN 203.0.113.24
You don't need an explicit deny, because the default policy already drops everything else. If you add one anyway, it must come after your allow rule. A deny that comes first blocks you too. If that already happened, move your allow rule to the top:
sudo ufw delete allow from 203.0.113.24 to any port 22 proto tcp
sudo ufw insert 1 allow from 203.0.113.24 to any port 22 proto tcp
Step 6: Test from a new terminal, then from somewhere else
Your current session proves nothing. A connection that was open before you enabled the firewall keeps working, even from a blocked address. Open a new terminal and connect again:
ssh deploy@your-server
Then try from an address that should be blocked, such as your phone's hotspot:
ssh: connect to host your-server port 22: Connection timed out
A timeout is what deny looks like: the server drops the packets and says nothing. If you'd rather see an instant Connection refused while you debug, use sudo ufw reject 22/tcp instead of deny. Both keep the connection out.
When both tests behave, cancel the safety net from Step 3.
Step 7: Add a second lock inside SSH
A firewall rule is one typo away from being wide open. SSH can check the source address too. Create a small config file:
echo 'AllowUsers [email protected]' | sudo tee /etc/ssh/sshd_config.d/10-allowlist.conf
sudo sshd -t && sudo systemctl reload ssh
sshd -t checks the config before you apply it. On Ubuntu the service is ssh, not sshd.
Keep this session open: a reload doesn't close it. Log in again from a new terminal before you close anything. If the new login fails, undo it from the session you still have:
sudo rm /etc/ssh/sshd_config.d/10-allowlist.conf && sudo systemctl reload ssh
Once it works, a login from any other address fails even if the firewall lets it through, and the server log says why:
User deploy from 198.51.100.7 not allowed because not listed in AllowUsers
AllowUsers lists who may log in at all, so add every account you use, each with its address.
What can go wrong
The IP rule is there, but everyone still gets in. An older ALLOW IN Anywhere rule sits in the list. Run sudo ufw status numbered and delete it (Step 5).
You're locked out, including from your own IP. A DENY rule sits above your ALLOW. Rules run top to bottom. Use the provider's web console to log in, then sudo ufw delete deny 22/tcp and add it again below your allow rule.
It worked yesterday and times out today. Your home IP changed. Most home connections get a new address now and then. Log in through the web console, add the new IP, and remove the old one. If it changes often, connect through a VPN with a fixed address, and allow that.
You get blocked after a few quick connections. That's ufw limit. It blocks an address that opens 6 or more connections within 30 seconds, which scripts, scp loops and Ansible runs can easily do. Use allow for your own IP and keep limit for anything wider.
A Docker container's port is still reachable from anywhere. Ports published with docker run -p are handled by Docker's own firewall rules, which ufw doesn't see. Publish internal ports on 127.0.0.1 only (-p 127.0.0.1:5432:5432), or don't publish them at all.
Blocked from your own machine even though the rule looks right. Your computer may be connecting over IPv6 while you allowed its IPv4 address. Check $SSH_CLIENT again and allow the address it shows, or connect with ssh -4.
The firewall turned itself off. That's the Step 3 timer doing its job. Fix the rule, then start the timer again before you retry.
FAQ
How do I allow SSH from only one IP address with ufw?
Run sudo ufw allow from YOUR_IP to any port 22 proto tcp, make sure the default incoming policy is deny, and delete any rule that allows port 22 from anywhere. Then test from a new terminal.
Do I need to restart anything after changing ufw rules?
No. ufw applies rules the moment you add them. Only changes to sshd_config need sudo systemctl reload ssh.
What if my home IP changes?
Use the provider's web console to add the new address. To avoid it, allow a VPN or office address that doesn't change, rather than a home connection.
Can I allow a range of IPs?
Yes: sudo ufw allow from 203.0.113.0/24 to any port 22 proto tcp allows 256 addresses. Allow the smallest range that covers you.
Should I use ufw or my cloud provider's firewall?
Use both if you can. The provider's firewall stops traffic before it reaches the server, and it still works if a package update or a script changes ufw. ufw on the server is the backup layer.
Is changing the SSH port enough instead?
No. A different port cuts log noise from bots, but anyone who scans the server finds it. An IP allowlist actually blocks them.
How do I get back in if I lock myself out?
Wait for the Step 3 timer if you set one. Otherwise open the provider's web or VNC console, log in, and run sudo ufw disable. Fix the rules, then enable it again.
Do it on a Hubfly space Box
A Hubfly space Box is a Linux VM with root access, so the ufw steps above work inside it. It also has its own Box firewall in front of the VM: a Box-level policy that each network interface inherits or overrides, with ordered rules that set the action, protocol, selectors and ports. A new Box allows only the inbound traffic you list, so check that list as well as ufw.
SSH reaches a Box through a TCP port mapping, and the public port is assigned for you. Connect with ssh -p <assigned-port> deploy@<box-address>; inside the VM, the ufw rule still uses port 22.
If SSH stops answering, the Box dashboard gives you a short-lived browser console, which is the recovery path from "What can go wrong".
Before you rely on rules inside the VM, run Step 1 on the Box. If $SSH_CLIENT doesn't show your own address, filter by IP in the Box firewall instead.
Go further on Hubfly space
- Not sure you need a full VM? Container vs VM for a side project compares both with real prices.
- Keep admin tools off the open internet entirely: Deploy your own MCP server with HTTPS and an access key shows access keys on a public endpoint.
- Running a web app rather than a VM? The Hubfly space firewall puts IP and CIDR rules, user-agent blocking and rate limits in front of each domain, with no server setup.
Try it on Hubfly space โ: create a Box, lock SSH to your IP, and keep the console as your way back in.
By Octave ยท drafted with AI, reviewed by Octave ยท Last updated 6 November 2026
Originally published on the Hubfly space blog.
Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes โ full credit and traffic to the original publisher.
