Back to tutorials
Tutorial

Port Knocking Tutorial (2026): Hide SSH on a VPS Without Changing Ports

Port knocking tutorial (2026) to hide SSH on your VPS using nftables + knockd, with safe rollback and troubleshooting.

By Anurag Singh
Updated on Aug 02, 2026
Category: Tutorial
Share article
Port Knocking Tutorial (2026): Hide SSH on a VPS Without Changing Ports

You can lock down SSH and still watch your logs fill up with bot probes every minute. The easiest way to cut that noise is to make SSH effectively disappear unless you “knock” first. This port knocking tutorial shows a production-safe setup on Ubuntu or Debian using knockd and nftables. It also includes a rollback path you can run fast if you misstep.

This pattern fits day-to-day hosting admin work. You get fewer SSH hits in logs, less exposed surface area, and a repeatable setup you can standardize across VPS nodes and dedicated servers.

What you’re building (and when you should skip it)

Port knocking adds a small access gate in front of SSH. Your server keeps port 22 closed to the public internet. When you send a predefined sequence of connection attempts (the “knock”) to a few closed ports, the server briefly opens SSH only for your source IP.

  • Best for: admin-only servers, single-tenant VPS, jump boxes, staging, and dedicated servers where you control who administers the host.
  • Not ideal for: admins with constantly changing IPs, teams that need predictable access, or environments where you already use a VPN / private network for admin traffic.

If your real problem is weak SSH basics (password auth, missing rate limits, no MFA, sloppy keys), fix that first. Port knocking is an extra gate, not a substitute.

If you want a clean baseline, run through our SSH hardening tutorial before adding “hide the port” behavior.

Prerequisites checklist (do this before touching firewall rules)

  • Ubuntu 24.04/26.04 or Debian 12/13 server (root or sudo access).
  • Console access (cloud/VPS console, IPMI/iKVM, or provider rescue mode). If you lock yourself out, this is how you get back in.
  • SSH key-based login already working.
  • Your current public IP noted (or your office/VPN egress IP).

Need a box where you can test safely? A HostMyCode VPS works well for this kind of tuning because you have full root access and consistent networking.

Step 1 — Install knockd and confirm nftables is available

On Ubuntu/Debian, nftables is the modern firewall backend. You’ll block SSH by default. Then you’ll allow it for a short window after a successful knock.

sudo apt update
sudo apt install -y knockd nftables

Confirm versions and service state:

knockd -V
sudo systemctl status knockd --no-pager
sudo nft --version

If you already use UFW, you can make this work. But the mental model gets messy fast. Here, you’ll manage rules directly with nftables so behavior stays obvious.

If you want help validating what’s actually exposed, see our firewall audit tutorial.

Step 2 — Put in a “do not lock me out” rule (temporary allowlist)

Before you make SSH disappear, add a temporary “known good” allow rule from your IP. This is your safety rope while you test.

Replace 203.0.113.10 with your public IP:

MYIP="203.0.113.10"

Create a minimal nftables ruleset that:

  • accepts established/related traffic
  • allows SSH from your IP (temporary)
  • allows HTTP/HTTPS if this is a web server
  • drops the rest inbound
sudo tee /etc/nftables.conf >/dev/null <<'EOF'
flush ruleset

table inet filter {
  chain input {
    type filter hook input priority 0;

    # Always keep loopback and established connections working
    iif "lo" accept
    ct state established,related accept

    # ICMP for basic network health (ping, PMTU)
    ip protocol icmp accept
    ip6 nexthdr icmpv6 accept

    # TEMP: allow SSH only from your admin IP (replace later)
    ip saddr 203.0.113.10 tcp dport 22 accept

    # Web server ports (optional but common on hosting boxes)
    tcp dport {80, 443} accept

    # Default deny
    counter drop
  }

  chain forward {
    type filter hook forward priority 0;
    counter drop
  }

  chain output {
    type filter hook output priority 0;
    accept
  }
}
EOF

Apply and enable at boot:

sudo systemctl enable --now nftables
sudo nft -f /etc/nftables.conf
sudo nft list ruleset

Open a second SSH session and leave it connected while you continue. If you break access, that session often makes the difference between a quick fix and a console recovery.

Step 3 — Create a dedicated “knock gate” in nftables

Next, you’ll add a dynamic set that temporarily holds “allowed SSH IPs”. After a successful knock, the server adds your IP to that set for (say) 10 minutes.

Edit /etc/nftables.conf and add a set + rule. Here’s a complete working example. Adjust the web ports to match your server:

sudo tee /etc/nftables.conf >/dev/null <<'EOF'
flush ruleset

table inet filter {
  # Dynamic allowlist populated by knockd
  set ssh_knock_allow {
    type ipv4_addr
    flags timeout
    timeout 10m
  }

  chain input {
    type filter hook input priority 0;

    iif "lo" accept
    ct state established,related accept

    ip protocol icmp accept
    ip6 nexthdr icmpv6 accept

    # SSH allowed only for IPs in the knock allowlist
    ip saddr @ssh_knock_allow tcp dport 22 accept

    # Web server ports (optional)
    tcp dport {80, 443} accept

    counter drop
  }

  chain forward {
    type filter hook forward priority 0;
    counter drop
  }

  chain output {
    type filter hook output priority 0;
    accept
  }
}
EOF

Apply it:

sudo nft -f /etc/nftables.conf
sudo nft list ruleset | sed -n '1,160p'

Important: after you apply this ruleset, SSH won’t accept new connections unless your IP is in ssh_knock_allow. You’ll populate that set via knockd in the next step. Keep your current SSH session open.

Step 4 — Configure knockd to open SSH for your IP

knockd listens for a sequence of TCP SYN packets to closed ports. When it sees the right sequence, it runs a command. In this setup, that command adds the source IP to the nftables set with a timeout.

Edit /etc/knockd.conf:

sudo nano /etc/knockd.conf

Use this configuration as a baseline:

[options]
    UseSyslog
    Interface = eth0

[openSSH]
    sequence    = 42123,53124,64125
    seq_timeout = 10
    tcpflags    = syn
    command     = /usr/sbin/nft add element inet filter ssh_knock_allow { %IP% timeout 10m }

[closeSSH]
    sequence    = 64125,53124,42123
    seq_timeout = 10
    tcpflags    = syn
    command     = /usr/sbin/nft delete element inet filter ssh_knock_allow { %IP% }
  • Interface: replace eth0 with your actual NIC (check with ip -br link).
  • sequence ports: pick high, random ports not used by your services.
  • timeout: 10 minutes is usually enough to authenticate and start work, without leaving a long exposure window.

On Ubuntu/Debian, knockd may be disabled by default. Enable it. Also make sure it binds to the same interface you set above:

sudo sed -i 's/^START_KNOCKD=.*/START_KNOCKD=1/' /etc/default/knockd
sudo sed -i 's/^KNOCKD_OPTS=.*/KNOCKD_OPTS="-i eth0"/' /etc/default/knockd

Restart and verify logs:

sudo systemctl enable --now knockd
sudo systemctl restart knockd
sudo journalctl -u knockd -n 50 --no-pager

Step 5 — Knock from your laptop, then SSH in

From your local machine, install a knock client. On macOS with Homebrew:

brew install knock

On Linux:

sudo apt install -y knockd
# package often provides 'knock' client too

Send the knock sequence to your server IP:

knock -v YOUR.SERVER.IP 42123 53124 64125

Now SSH should work for the timeout window:

ssh -i ~/.ssh/id_ed25519 user@YOUR.SERVER.IP

On the server, confirm your IP is in the allowlist set:

sudo nft list set inet filter ssh_knock_allow

If you want to close early, run the close sequence:

knock -v YOUR.SERVER.IP 64125 53124 42123

Step 6 — Remove always-allow SSH and switch to “knock only”

If you left any temporary allow rules in place, remove them now. In this tutorial, SSH is already “knock only” because the ruleset permits port 22 only from the dynamic set.

Test the failure mode:

  • From a network that hasn’t knocked, confirm port 22 is closed/filtered: nc -vz YOUR.SERVER.IP 22.
  • Knock, then immediately re-test: it should connect.
  • Wait 10 minutes and verify SSH closes again.

Hardening the knock itself (practical improvements)

Port knocking cuts noise, but don’t treat it as security theater. These upgrades make a real difference on hosting servers.

1) Use a longer sequence and slower timeout

Three ports works. Five ports lowers the chance of accidental matches from background scans. Keep seq_timeout tight (8–12 seconds) so random traffic won’t stumble into the sequence.

2) Limit who can ever be opened (geo/IP allowlist)

If admins always come from a known IP block (office, VPN), enforce that at the firewall. Only your expected sources should be able to send packets to the knock ports.

Example: only allow knocks from your office IP to reach the box (the ports are still “closed”, but at least the packets arrive):

# Add before the final drop in chain input
ip saddr 203.0.113.10 tcp dport {42123,53124,64125} accept

Then keep dropping everything else. From the rest of the internet, the knock sequence is dead on arrival.

3) Keep SSH sane: keys only, no root, modern ciphers

Don’t relax SSH just because it’s hidden most of the time. Keep keys-only auth, disable root, and stick to modern defaults.

If you want a full checklist, follow our SSH hardening tutorial and place this gate in front of it.

Troubleshooting: why the knock “works” but SSH still fails

Most breakage comes from four causes: the wrong interface, a mismatch in nftables table/set names, NAT changing your source IP, or provider filtering that interferes with the knock traffic.

Check 1 — knockd is listening on the right interface

ip -br addr
sudo grep -E 'Interface|command|sequence' -n /etc/knockd.conf
sudo journalctl -u knockd -n 80 --no-pager

If the interface is wrong, you’ll see no events when you knock. Fix Interface and KNOCKD_OPTS, then restart knockd.

Check 2 — nftables command path and table name match

Your command references:

  • table: inet filter
  • set: ssh_knock_allow

Verify those exist:

sudo nft list ruleset | grep -n "table inet filter" -n
sudo nft list set inet filter ssh_knock_allow

If you used a different table name, update the command in /etc/knockd.conf.

Check 3 — you’re knocking from the same source IP you SSH from

The allow is tied to the IP that knocked. If you knock from laptop Wi‑Fi and then SSH from a phone hotspot, the server won’t connect the dots.

To confirm what knockd saw, watch logs while you knock:

sudo journalctl -u knockd -f

Check 4 — you’re locked out and need to roll back fast

If you can’t SSH in, use your provider console and apply a permissive emergency ruleset. Get access back first. Then iterate with two SSH sessions open.

sudo tee /etc/nftables.conf >/dev/null <<'EOF'
flush ruleset

table inet filter {
  chain input {
    type filter hook input priority 0;
    iif lo accept
    ct state established,related accept
    tcp dport 22 accept
    tcp dport {80,443} accept
    ip protocol icmp accept
    ip6 nexthdr icmpv6 accept
    counter drop
  }
  chain output { type filter hook output priority 0; accept }
}
EOF
sudo nft -f /etc/nftables.conf
sudo systemctl restart knockd

Once you’re back in, re-apply the proper ruleset and test again with two SSH sessions open.

Operational checklist for hosting teams

  • Document the knock sequence in your password manager (and treat it like a secret).
  • Define a rotation schedule (quarterly works) to change the knock ports.
  • Keep an out-of-band path (provider console, rescue mode, or iKVM) for every server.
  • Log review: check journalctl -u knockd weekly for unexpected open events.
  • Backups first: take a snapshot before firewall changes so rollback is a click away.

For a clean backup workflow on Ubuntu, pair this with our VPS snapshot backup tutorial. Firewall work is much less stressful when you can restore quickly.

Summary: a quieter SSH surface with minimal workflow change

With this in place, port 22 stays closed to the internet and opens only for your IP after a correct knock. You’ll see fewer random probes, reduce accidental exposure, and still keep SSH configured the right way.

If you’re rolling this out across multiple servers or client environments, a managed VPS hosting plan from HostMyCode can help with firewall policy, change windows, and disciplined rollback procedures.

If you prefer to run everything yourself, start with a HostMyCode VPS and bake this into your standard image.

If you’re hardening SSH across a production VPS fleet, you need changes you can document and roll back under pressure. HostMyCode offers server plans that fit that workflow, from a self-managed HostMyCode VPS to hands-on managed VPS hosting when you don’t want to debug firewall rules at 2 a.m.

FAQ

Does port knocking replace Fail2Ban?

No. Port knocking hides SSH until you open it, but you should still rate-limit and block abusive attempts during the open window. Use it as an extra gate, not your only control.

Is it safer to change the SSH port instead?

Changing ports cuts noise, but the port stays reachable all the time. Port knocking keeps SSH closed until you authenticate with the knock sequence. You can combine both, but only add complexity if you’ll actually maintain it.

Will this break cPanel/WHM or shared hosting workloads?

It can, especially if automation expects persistent SSH access. For cPanel servers, plan the change, test it, and do it in a maintenance window. If you’re still working on the overall WHM security posture, start with our cPanel hardening tutorial and add port knocking only if it fits your admin workflow.

What if my admin IP changes often?

Port knocking still works, but you’ll knock from the new IP each time. If your IP changes several times a day, a VPN or jump host usually gives you a cleaner workflow. See our SSH jump host setup tutorial.

How do I confirm SSH is truly hidden?

From a network that hasn’t knocked, run nmap -p 22 YOUR.SERVER.IP or nc -vz YOUR.SERVER.IP 22. It should show closed/filtered. Then knock and re-run the test within the timeout window.