A firewall decides which network traffic may enter a VPS, leave it, or be forwarded through it. vpsFree does not enable a shared firewall in front of your VPS. The initial state therefore depends on the distribution: NixOS, for example, enables its firewall by default, while a clean Debian or Ubuntu installation usually has no restrictive local rules.
Running your own firewall is not mandatory. It can be reasonable to run only the required, regularly updated services and leave the rest stopped. A firewall adds another layer of protection, but it does not replace secure configuration, updates, or SSH key authentication.
A network program listens on a combination of an address, a protocol (usually
TCP or UDP) and a port. The web commonly uses TCP ports 80 and 443; SSH uses
TCP port 22. The command ss -lntup shows which services are listening.
0.0.0.0 means every IPv4 address, [::] every IPv6 address, while
127.0.0.1 and [::1] are reachable only inside the VPS.
When filtering on the VPS itself, you will encounter three basic chains:
Rules in a chain are evaluated from top to bottom. Once a packet matches a
rule with a terminal action such as ACCEPT, DROP, or REJECT, no
later rules are tried. If no rule decides, the chain's default policy applies.
A stateful firewall can also recognise packets belonging to an established
connection, allowing replies before rules for new connections are considered.
IPv4 and IPv6 are separate address families. A firewall must protect both; allowing a service only for IPv4 does not hide it on a public IPv6 address.
Choose one firewall management method. Do not combine hand-written
iptables or nftables rules with UFW or firewalld unless you understand
how they interact. The simple rules below assume a VPS without Docker, a VPN,
routing, or another service that creates its own firewall rules.
The iptables syntax is often approachable for a first low-level setup. On
current Debian systems, the iptables commands normally use the nftables
backend; iptables -V reports nf_tables. The iptables command
handles IPv4 and the separate ip6tables command handles IPv6.
This script for a clean VPS permits loopback, ICMP, established connections,
and new TCP connections on ports 22, 80, and 443. INPUT and FORWARD end with a
DROP policy, while OUTPUT remains ACCEPT. The script first sets
accepting policies, builds the rules, and enables the blocking policies only at
the end:
#!/usr/bin/env bash set -eu # Install the command-line tools and boot-time persistence. apt update apt install -y iptables iptables-persistent # Keep traffic allowed while replacing the current rules. iptables -P INPUT ACCEPT iptables -P FORWARD ACCEPT iptables -P OUTPUT ACCEPT ip6tables -P INPUT ACCEPT ip6tables -P FORWARD ACCEPT ip6tables -P OUTPUT ACCEPT # Remove the current filter rules and user-defined chains. iptables -F iptables -X ip6tables -F ip6tables -X # IPv4 input: drop invalid packets, keep established traffic, and open services. iptables -A INPUT -m conntrack --ctstate INVALID -j DROP iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT iptables -A INPUT -i lo -j ACCEPT iptables -A INPUT -p icmp -j ACCEPT iptables -A INPUT -p tcp -m multiport --dports 22,80,443 -j ACCEPT # IPv6 input: apply the same policy and keep IPv6 control traffic working. ip6tables -A INPUT -m conntrack --ctstate INVALID -j DROP ip6tables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT ip6tables -A INPUT -i lo -j ACCEPT ip6tables -A INPUT -p ipv6-icmp -j ACCEPT ip6tables -A INPUT -p tcp -m multiport --dports 22,80,443 -j ACCEPT # Drop other input and forwarded traffic; keep locally generated output allowed. iptables -P INPUT DROP iptables -P FORWARD DROP ip6tables -P INPUT DROP ip6tables -P FORWARD DROP # Persist the rules and display the resulting input policy. netfilter-persistent save iptables -L INPUT -n -v ip6tables -L INPUT -n -v
Result: On IPv4 and IPv6, loopback, ICMP, established traffic, and new TCP connections to ports 22, 80, and 443 pass. Other input and all forwarded traffic are dropped, while traffic originating from the VPS remains allowed.
iptables-persistent saves rules for both address families and
netfilter-persistent loads them again during boot. Run
netfilter-persistent save after later changes. See the
iptables(8)
and
netfilter-persistent(8)
manuals for more options.
nftables manages IPv4 and IPv6 in one rules language. Save the following as
/etc/nftables.conf. The inet family table handles both address families
and the rule order follows the explanation above:
#!/usr/sbin/nft -f # Replace the complete current ruleset. flush ruleset table inet filter { chain input { type filter hook input priority filter; policy drop; # Handle state first, trust loopback, keep control traffic, and open services. ct state invalid drop ct state established,related accept iifname "lo" accept meta l4proto { icmp, ipv6-icmp } accept tcp dport { 22, 80, 443 } accept } chain forward { # This simple host firewall does not forward traffic. type filter hook forward priority filter; policy drop; } chain output { # Locally generated traffic is unrestricted. type filter hook output priority filter; policy accept; } }
Result: One inet table applies the same policy as the previous example
to IPv4 and IPv6: it permits loopback, ICMP, established connections, and new
TCP connections to ports 22, 80, and 443; drops other input and forwarding;
and permits outgoing traffic.
First validate the configuration without applying it by using nft -c.
Then enable the service that loads it now and on subsequent boots:
#!/usr/bin/env bash set -eu # Install nftables. apt update apt install -y nftables # Validate the complete configuration before applying it. nft -c -f /etc/nftables.conf # Load the configuration now and at boot, then display the result. systemctl enable --now nftables nft list ruleset
After an edit, validate the file again with nft -c -f
/etc/nftables.conf and load it with systemctl reload nftables. See the
Debian wiki and the
nft(8) manual for
details.
UFW provides a simpler interface to a stateful firewall. This example adds
rules that permit SSH and web traffic. Existing user rules remain in place,
and built-in rules continue to handle loopback and required network control
traffic. For traffic not matched by those rules, the defaults drop new
incoming connections and allow outgoing traffic. The default installation
manages IPv4 and IPv6; before activation, the script verifies that
/etc/default/ufw contains IPV6=yes:
#!/usr/bin/env bash set -eu apt update apt install -y ufw ufw default deny incoming ufw default allow outgoing ufw allow 22/tcp comment 'SSH' ufw allow 80/tcp comment 'HTTP' ufw allow 443/tcp comment 'HTTPS' ufw show added grep -Fx 'IPV6=yes' /etc/default/ufw ufw --force enable ufw status verbose
Result: On IPv4 and IPv6, UFW permits established connections and new TCP connections to ports 22, 80, and 443. Existing user rules and UFW's built-in rules remain in effect. For otherwise-unmatched traffic, the defaults drop new incoming connections and allow outgoing traffic.
If the IPV6=yes check fails, UFW is not enabled; fix the setting first.
For example, remove a rule with ufw delete allow 80/tcp. The command ufw
status numbered displays a numbered list. See the
Ubuntu firewall documentation
for details.
firewalld groups interfaces and rules into zones and can refer to services by
name. The following setup uses the default public zone, permanently permits
SSH and web traffic in it, and then loads the permanent configuration:
#!/usr/bin/env bash set -eu dnf install -y firewalld systemctl enable --now firewalld firewall-cmd --set-default-zone=public firewall-cmd --permanent --zone=public --add-service=ssh firewall-cmd --permanent --zone=public --add-service=http firewall-cmd --permanent --zone=public --add-service=https firewall-cmd --reload firewall-cmd --get-active-zones firewall-cmd --zone=public --list-all
Result: On IPv4 and IPv6, the public zone permits the ssh, http,
and https services in addition to services and rules already present in
the zone. All other incoming traffic remains governed by the zone's existing
rules and target. This setup does not restrict outgoing traffic from the VPS.
Use the zone that firewall-cmd –get-active-zones reports for the VPS
interface; it is not always named public. A change without –permanent
lasts only until a restart or configuration reload, while a permanent change
becomes active only after firewall-cmd –reload. The
firewalld documentation
contains more examples.
NixOS enables networking.firewall.enable by default. Declare open ports
declaratively. You can save the following module as
/etc/nixos/firewall.nix and add ./firewall.nix to the existing
imports list in /etc/nixos/configuration.nix:
{ ... }:
{
networking.firewall = {
enable = true;
allowedTCPPorts = [ 22 80 443 ];
};
}
Result: On IPv4 and IPv6, the module adds TCP ports 22, 80, and 443 to the allowed new connections. The firewall continues to permit established connections and built-in network control traffic. Its default policy drops other new incoming traffic not allowed by this or another module. The module does not restrict outgoing traffic.
Some services have an openFirewall option that opens the required ports
automatically. Check what the module configures before making a change. Apply
the first change with nixos-rebuild test:
#!/usr/bin/env bash set -eu nixos-rebuild test
Test a new SSH connection. Only after it works, persist the configuration in a new boot generation:
#!/usr/bin/env bash set -eu nixos-rebuild switch
See the firewall section of the NixOS manual.
A port published by Docker may pass through rules that Docker creates and may
not behave as expected from UFW or firewalld. Do not assume that deny
incoming by itself protects every container. Publish only ports intended to
be public; for example, bind a service used by a local reverse proxy to
127.0.0.1:8080:80. Also test reachability from another machine after
deployment.
The Docker Engine firewall documentation describes the packet-filtering behaviour and available controls.
If a new SSH connection fails after a firewall change, do not close the
original session. Use it to inspect ss -lntp and the manager you chose:
iptables -L -n, nft list ruleset, ufw status verbose, or
firewall-cmd –get-active-zones.
If no SSH session remains, the remote console guide walks you through recovery. The start menu gives you a recovery path that does not depend on working networking or an allowed SSH port. During a restart, Run shell provides a shell directly in the VPS file system from which you can repair the rules.
In a normally running system, temporarily disable UFW with ufw disable,
firewalld with systemctl stop firewalld, or nftables with systemctl stop
nftables. With hand-written iptables, first set the IPv4 and IPv6 INPUT
policies to ACCEPT and only then repair the rules.
systemd and the firewall manager are not running in the Run shell
environment. Restore the persistent configuration there, such as
/etc/iptables/rules.v4 and /etc/iptables/rules.v6 or
/etc/nftables.conf, leave the shell, and start the system normally. On
NixOS, you can instead select the previous working generation in the start menu
and then repair the configuration.
Temporarily disabling the firewall removes a protective layer. Enable it again afterward and test a new SSH connection. Do not blindly delete low-level rules when they may belong to Docker, a VPN, or another service.