Back to blog
Blog

Linux VPS iptables to nftables migration in 2026: a zero-surprise cutover plan with verification and rollback

Linux VPS iptables to nftables migration in 2026 with a safe cutover plan, verification checks, common pitfalls, and rollback steps.

By Anurag Singh
Updated on Apr 19, 2026
Category: Blog
Share article
Linux VPS iptables to nftables migration in 2026: a zero-surprise cutover plan with verification and rollback

You can run a perfectly healthy Linux VPS and still lose a weekend to firewall drift: legacy iptables rules in one place, firewalld or Docker chains in another, and nobody’s sure what actually decides whether traffic gets through.

This Linux VPS iptables to nftables migration guide focuses on a cutover you can predict. You’ll inventory what’s live, restate the policy in plain language, stage an nftables ruleset alongside what you have, then switch with a rollback path that doesn’t sacrifice your SSH session.

Why migrate now (and what changes in practice)

nftables has been the default direction for years. The reason to move in 2026 isn’t trend-chasing; it’s day-to-day clarity. You get one ruleset, consistent ordering, and a single file you can audit and back up.

  • Fewer “hidden” rules: you review one nft ruleset instead of juggling tables, chains, and compatibility layers.
  • Better maintainability: sets and maps replace long, repetitive allowlists and rate-limit rules.
  • Cleaner automation: export/restore becomes a predictable deployment step, not a fragile ritual.

If you run Docker or Kubernetes, they’ll still add dynamic rules. That’s fine. The point is to own your baseline policy and document what’s yours versus what your orchestrator generates.

Prerequisites and a safety checklist before you touch the firewall

This assumes you’re comfortable administering Linux. The example is a single VPS that serves a public web endpoint and an internal API. Commands follow Debian 12/13-style tooling, but the workflow applies to Ubuntu and most modern distros.

  • A VPS with console access (provider web console or out-of-band shell). Don’t rely only on SSH.
  • Root access (or sudo with full privileges).
  • Basic understanding of inbound vs outbound and your required ports.
  • Known required services/ports for this example:
    • SSH: 22/tcp (or your custom port)
    • HTTP/HTTPS: 80/tcp, 443/tcp
    • Internal API: 8443/tcp only from a private office IP range

If this is production, take a snapshot first. Treat firewall changes like schema changes: reversible, tested, and easy to unwind. If you don’t have a snapshot habit yet, start with Linux VPS snapshot backups.

Want a clean place to rehearse and standardize policy? Spin up a small instance and practice the cutover end-to-end. A HostMyCode VPS works well for staging, testing, and then copying the same rules across nodes.

Linux VPS iptables to nftables migration: your cutover plan (high level)

Use this order and you avoid most surprises:

  1. Inventory the current firewall state (including compatibility layers).
  2. Write down required inbound and outbound flows in plain English.
  3. Build an nftables ruleset that expresses that intent.
  4. Load it in a controlled way (without enabling persistence yet).
  5. Verify connectivity from the outside and the inside.
  6. Enable persistence and document the new source of truth.
  7. Keep an explicit rollback route.

Step 1: Inventory your current iptables rules (and identify the backend)

Start by confirming which iptables “mode” you’re using: legacy backend, or the nft-compatible frontend. The version string makes it obvious.

iptables --version
update-alternatives --display iptables 2>/dev/null || true

Expected output examples:

  • iptables v1.8.x (nf_tables) means iptables commands are being translated into nft rules.
  • iptables v1.8.x (legacy) means you’re on the older backend (common on older images or hand-rolled setups).

Now capture what’s active. Even if you think you know the rules, save them anyway—this becomes your rollback artifact.

iptables-save > /root/iptables-save.before-nft.txt
ip6tables-save > /root/ip6tables-save.before-nft.txt

Then sanity-check what’s actually in effect:

iptables -S
iptables -L -n -v

Also check for higher-level managers that may overwrite your changes on reload or reboot:

systemctl is-active firewalld 2>/dev/null || true
systemctl is-active ufw 2>/dev/null || true

If firewalld or ufw is active, decide up front who owns the firewall. Either move those tools into an nft-backed approach, or disable them and manage nft directly. Mixing managers is how you get “it worked last Tuesday” outages.

Step 2: Translate rules into traffic intent (the part people skip)

Before writing nft syntax, write the policy as if you’re handing it to a teammate:

  • Inbound default: drop.
  • Allow established/related packets.
  • Allow loopback.
  • Allow SSH from anywhere (or from your admin IP range).
  • Allow HTTP/HTTPS from anywhere.
  • Allow TCP/8443 only from 203.0.113.0/24 (example office range).
  • Outbound default: allow (common for VPS workloads), with optional DNS/NTP allowances if you want stricter egress later.

This is where the missing dependencies surface—outbound SMTP to a relay, callbacks to a third-party API, or a monitoring agent that phones home. If you need an audit trail later, plan for centralized logging as a separate step; see Linux VPS compliance logging.

Step 3: Install nftables and create a staged ruleset file

Install the tools and put your rules in a dedicated file. On Debian/Ubuntu, this layout is conventional and works cleanly with systemd.

apt-get update
apt-get install -y nftables

Create /etc/nftables.d/hostmycode-vps.nft with a small, readable ruleset. This example uses an inet table so IPv4 and IPv6 share one policy.

cat > /etc/nftables.d/hostmycode-vps.nft <<'EOF'
#!/usr/sbin/nft -f

flush ruleset

table inet hmc_filter {
  set office_ipv4 {
    type ipv4_addr
    flags interval
    elements = { 203.0.113.0/24 }
  }

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

    # Always allow loopback
    iif "lo" accept

    # Keep existing connections working
    ct state established,related accept

    # Basic ICMP for diagnostics (adjust if you must)
    ip protocol icmp accept
    ip6 nexthdr icmpv6 accept

    # SSH
    tcp dport 22 ct state new accept

    # Web
    tcp dport { 80, 443 } ct state new accept

    # Internal API only from office IP range
    ip saddr @office_ipv4 tcp dport 8443 ct state new accept

    # Drop the rest (policy drop)
  }

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

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

chmod 0644 /etc/nftables.d/hostmycode-vps.nft

Why the explicit flush ruleset? It clears leftovers from earlier experiments so you don’t debug ghost tables later. If you already rely on orchestrator-managed nft rules, do not flush globally. In that case, create a dedicated table and avoid global flush; instead, flush only your table (example: flush table inet hmc_filter) and leave Docker/Kubernetes tables alone.

Step 4: Dry-run the ruleset and load it temporarily

Check syntax first. This validates the file without changing packet flow.

nft -c -f /etc/nftables.d/hostmycode-vps.nft
echo $?

Expected output: exit code 0 and no error text.

Then load it. Run this inside a persistent shell (tmux/screen) so a brief disconnect doesn’t end the session.

nft -f /etc/nftables.d/hostmycode-vps.nft
nft list ruleset | sed -n '1,160p'

Expected output: table inet hmc_filter plus the three chains with the policies you set.

Step 5: Verification from both sides (don’t trust “it loaded”)

“Rules loaded” only means the parser was happy. You still need to prove traffic behaves the way you intended—both locally and from the outside.

On the VPS, confirm the services are actually listening:

ss -lntp | awk 'NR==1 || /:(22|80|443|8443) /'

Expected output includes your service sockets (for example, LISTEN 0 4096 0.0.0.0:443 for Nginx).

From your workstation, test the open ports:

nc -vz your.vps.ip 22
nc -vz your.vps.ip 443

Expected output: succeeded (or similar). If you don’t have nc, use ssh -v for port 22 and curl -I for HTTPS.

Test the restricted port (8443) from an allowed IP (office network) and from a disallowed one (mobile hotspot). From the disallowed network, you want failure:

nc -vz your.vps.ip 8443

Expected output: timeout or refusal (timeout is typical for a firewall drop).

Check counters to confirm rules are matching real traffic:

nft list chain inet hmc_filter input

After a few attempts, packet/byte counters should increase on the rules you hit (depending on distro defaults and kernel config).

Step 6: Make nftables persistent (and stop iptables from reappearing)

Once checks pass, make the rules survive reboot. On many distros, the service loads from /etc/nftables.conf, so have that file include your rules directory.

cat > /etc/nftables.conf <<'EOF'
#!/usr/sbin/nft -f

include "/etc/nftables.d/*.nft"
EOF

chmod 0644 /etc/nftables.conf
systemctl enable --now nftables
systemctl status nftables --no-pager

If you previously used an iptables persistence service, disable it now. You want exactly one component restoring firewall state at boot.

systemctl disable --now netfilter-persistent 2>/dev/null || true
systemctl disable --now iptables 2>/dev/null || true

You now have an actual source-of-truth file you can commit to infrastructure code or configuration management.

Common pitfalls that break real servers

  • Forgetting established/related: without ct state established,related accept, SSH may act flaky and outbound connections can fail in ways that don’t point at the firewall.
  • Flushing rulesets on hosts with Docker/Kubernetes: a global flush ruleset can remove chains created by Docker, CNI plugins, or load balancers. If you rely on those, flush only your own table.
  • IPv6 surprises: if IPv6 is enabled and you only filter IPv4, “closed” services may still be reachable over IPv6. An inet table keeps policies aligned.
  • Firewall managers fighting you: ufw, firewalld, and custom scripts can overwrite your work on reboot.
  • Misplaced trust in cloud security groups: cloud ACLs help, but they don’t replace host policy. Treat them as an outer layer, not the only layer.

If you’re also tightening security baselines, pair this change with a broader review: Linux VPS hardening checklist.

Rollback plan: get back to iptables in under 2 minutes

A rollback shouldn’t live in your head. Write it down, keep it close, and assume you’ll execute it from the provider console.

Rollback path A (fastest): disable nftables and restore iptables-save

# From VPS console
systemctl disable --now nftables

iptables-restore < /root/iptables-save.before-nft.txt
ip6tables-restore < /root/ip6tables-save.before-nft.txt

Rollback path B: temporarily open SSH while you debug (use only if you’re locked out and need a quick door back in):

nft add rule inet hmc_filter input tcp dport 22 accept

Once you’re back in, check chain ordering, fix the rule file, and reload from disk.

Operational hygiene: test changes safely and keep them auditable

After you migrate, the most common self-inflicted failure is a “quick hot-fix” typed into prod. Prevent that with two simple habits: treat the file as authoritative, and run the same checks every time.

  • Always reload from file: treat /etc/nftables.d/hostmycode-vps.nft as the only editable artifact.
  • Keep a change window guardrail: run a quick preflight and a postflight check every time.

Drop this preflight/postflight pair into your runbook:

# Preflight
nft -c -f /etc/nftables.d/hostmycode-vps.nft && echo "Syntax OK"
ss -lnt | grep -E ':(22|80|443|8443) '\
  && echo "Ports listening" || echo "Check services"

# Apply
nft -f /etc/nftables.d/hostmycode-vps.nft

# Postflight
nft list table inet hmc_filter

Package updates and reboots can still surprise you if your patching process is ad hoc. For multi-node fleets, see Linux VPS automated patch management for a “safe reboot + reporting + rollback” approach that pairs well with disciplined firewall changes.

Next steps after migration (what to improve once the basics are stable)

  • Add rate limiting for SSH using nft meters or limits, rather than stacking lots of one-off rules.
  • Introduce structured firewall logging for drops, but keep it quiet (log new connections, sample when needed).
  • Automate backups of your ruleset alongside system config. If you already use restic, include /etc/nftables.conf and /etc/nftables.d/.
  • Pair with secrets and TLS hygiene if the VPS hosts internal APIs. Firewalls help, but they’re rarely the only control you need.

If you’re standardizing firewall policy across multiple servers, start on a VPS you fully control and can snapshot quickly. A HostMyCode VPS gives you consistent networking and root access for repeatable nftables rollouts. If you’d rather have OS upkeep handled so you can stay focused on application reliability, consider managed VPS hosting from HostMyCode (Affordable & Reliable Hosting).

FAQ

Do I need to remove iptables packages after switching to nftables?

Usually no. What matters is which service restores rules at boot and what your automation calls. Keep iptables installed if other software expects the command, but make sure nftables is the active, persistent source of policy.

Can I migrate without downtime?

Yes, as long as you stage carefully and verify from outside before enabling persistence. Most downtime comes from flushing existing rules or accidentally blocking SSH.

What if Docker breaks after I load my nft ruleset?

If you used a global flush ruleset, you probably removed Docker-managed tables/chains. Restarting Docker may recreate some rules, but the correct fix is to avoid global flush and scope changes to your own table.

Should I default-drop outbound traffic too?

Only if you’ve inventoried required egress (DNS, NTP, package repos, third-party APIs, SMTP relays, observability endpoints). For many VPS workloads, default-allow outbound is a sensible baseline, then tighten it as you confirm real flows.

Summary

A clean nftables policy gives you one place to reason about firewall behavior, audit changes, and recover quickly. Inventory what’s running, translate policy intent, load rules from a file, and verify from both sides. Keep rollback commands written down and actually test them once.

If you plan to roll this across environments, do it on a foundation with reliable access and snapshots. Start on a HostMyCode VPS (or step up to managed VPS hosting if you want less operational overhead) and treat /etc/nftables.d/ as configuration—not tribal knowledge.