Introduction
This tutorial shows how to setup a generic NAT gateway for Cloud Servers via private Cloud Networks. It explains how to create the Network and Cloud Servers, how to setup the routing, and how to achieve a persistent configuration.
By the end of this tutorial, you will be able to access the public network from your private-network-only server (client server) by routing traffic via a server with a public IP address (NAT server). Both servers have to be in the same private Cloud Network.
If you are specifically interested in using pfSense, you can use this tutorial.
Prerequisites
-
This tutorial is written for:
- Ubuntu 18.04, 20.04, 22.04, and 24.04
- Debian 10, 11 and 12
- CentOS 7
- CentOS Stream 8 and 9
- Fedora 36 and 37
- Rocky Linux 8 and 9.
Example terminology
- Network:
10.0.0.0/16
- Gateway:
10.0.0.1
- NAT server:
10.0.0.2
Please replace 10.0.0.0/16
with your own network, 10.0.0.1
with your own network gateway IP, and 10.0.0.2
with the private IP of your own NAT server in all example commands.
Step 1 - Creating the Network and servers
Open the Hetzner Cloud Console and select your project.
You can create your Network first or, alternatively, together with your servers at server creation.
-
Create the servers
In your project, select:
Servers
>Add Server
You need at least two servers. The NAT server needs an IPv4 address, the other servers don't.
Under
Networking
, you have to select the network you just created.If you don't want to setup the routes manually as explained in the steps below, you can use cloud-init as explained in "Step 6 - Cloud-init" instead.
Step 2 - Adding the route to the Network
In order for our setup to work properly, we need to add the following route to the Network:
Destination: 0.0.0.0/0
Gateway: 10.0.0.2
The gateway should be the IP address of the NAT server on which you configure masquerading.
Step 3 - Configuring NAT
To configure the NAT server, we will use the following commands:
-
Enable IP forwarding, since it is disabled by default
echo 1 > /proc/sys/net/ipv4/ip_forward
-
Add a rule to the 'nat' table
iptables -t nat -A POSTROUTING -s '10.0.0.0/16' -o eth0 -j MASQUERADE
Let's look at the second command in more detail:
iptables
➜ the program we are using-t nat
➜ choose the table 'nat'-A POSTROUTING
➜ add a rule to postrouting-s '10.0.0.0/16'
➜ target packets from the source '10.0.0.0/16'-o eth0
➜ output at 'eth0'-j MASQUERADE
➜ masquerade the packages with the 'routers' IP
To configure the client servers, we only need to add a default route.
-
For example, like this:
ip route add default via 10.0.0.1
Click here, if you get RTNETLINK answers: File exists
If you get the error
RTNETLINK answers: File exists
, run this command to check if you already have a default route:ip route
Example output:
default via 172.31.1.1 dev eth0 10.0.0.0/16 via 10.0.0.1 dev enp7s0 10.0.0.1 dev enp7s0 scope link 172.31.1.1 dev eth0 scope link
You can remove the existing default route with this command:
ip route del default
After it was removed, you can try adding the new route again:
ip route add default via 10.0.0.1
Step 4 - Achieving a persistent configuration
The example commands below for Debian and Ubuntu use vim
, which can be installed using: apt install vim
The next steps depend on the OS of your server:
Debian 10 / 11 / 12, and Ubuntu 18.04
-
Update
First, the system needs to be updated:
apt update && apt upgrade -y
Ubuntu 22.04 additionally requires:
apt install ifupdown
-
On the NAT server
To make everything persistent, we open the following file:
vim /etc/network/interfaces
To enter the insert mode in
vim
, pressi
and append the following to the file:auto eth0 iface eth0 inet dhcp post-up echo 1 > /proc/sys/net/ipv4/ip_forward post-up iptables -t nat -A POSTROUTING -s '10.0.0.0/16' -o eth0 -j MASQUERADE
To save the file, press
esc
to escape the insert mode, then type:x
or:wq
and hit ENTER.
-
On the client servers
Since we also want the route to be persistent, we edit the following file:
vim /etc/network/interfaces
And append:
auto ens10 iface ens10 inet dhcp post-up ip route add default via 10.0.0.1
Ubuntu 22.04 / 24.04
-
Update
First, the system needs to be updated:
apt update && apt upgrade -y
-
On the NAT server
To make everything persistent, we open the file in
/etc/netplan
:vim /etc/netplan/50-cloud-init.yaml
Check the following information. If everything looks fine, press
esc
followed by:q
and ENTER to close the file. To enter the insert mode invim
, pressi
.network: version: 2 ethernets: eth0: dhcp4: true
To save the file, press
esc
to escape the insert mode, then type:x
or:wq
and hit ENTER.Now create a new file in
/etc/networkd-dispatcher/routable.d
:vim /etc/networkd-dispatcher/routable.d/10-eth0-post-up
To enter the insert mode in
vim
, pressi
and add the following to the file:#!/bin/bash echo 1 > /proc/sys/net/ipv4/ip_forward iptables -t nat -A POSTROUTING -s '10.0.0.0/16' -o eth0 -j MASQUERADE
To save the file, press
esc
to escape the insert mode, then type:x
or:wq
and hit ENTER.Now add execute permissions:
chmod +x /etc/networkd-dispatcher/routable.d/10-eth0-post-up
-
On the client servers
Since we also want the route to be persistent, we edit the following file:
vim /etc/systemd/network/10-ens10.network
And add:
[Match] Name=ens10 [Network] DHCP=yes Gateway=10.0.0.1
Ubuntu 20.04
-
Update
First, the system needs to be updated:
apt update && apt upgrade -y
Ubuntu 20.04 uses
netplan
instead of/etc/interfaces
by default. To achieve persistent configuration, the networkd-dispatcher is being used.As mentioned in the netplan FAQ, the
networkd-dispatcher
equivalent ofpost-up
is placing a script in/etc/networkd-dispatcher/routable.d/
. In this tutorial, we call the script50-masq
but the name doesn't matter.
-
On the NAT server
Create the file:
vim /etc/networkd-dispatcher/routable.d/50-masq
To enter the insert mode in
vim
, pressi
and append the following to the file:#!/bin/sh /bin/echo 1 > /proc/sys/net/ipv4/ip_forward /sbin/iptables -t nat -A POSTROUTING -s '10.0.0.0/16' -o eth0 -j MASQUERADE
To save the file, press
esc
to escape the insert mode, then type:x
or:wq
and hit ENTER.The following command is required to make the script executable, otherwise it will not work:
chmod +x /etc/networkd-dispatcher/routable.d/50-masq
-
On the client servers
Create the file:
vim /etc/networkd-dispatcher/routable.d/50-masq
And append:
#!/bin/sh /sbin/ip route add default via 10.0.0.1
Finally, make it executable:
chmod +x /etc/networkd-dispatcher/routable.d/50-masq
CentOS 7, CentOS Stream 8 / 9, Rocky Linux 8 / 9, Fedora 36 / 37
-
Update
First, the system needs to be updated:
yum update -y && yum upgrade -y
We use the
NetworkManager
'sdispatcher.d
to run our scripts automated on start. This is done by placing the script into the folder/etc/NetworkManager/dispatcher.d/
. Here, the name determines the execution condition of the script. More information can be found here.In this tutorial we use the name
ifup-local
whereifup
is the condition for the script to get executed.
-
On the NAT server
Fedora 36 / 37 additionally require:
yum install iptables -y
Create the file:
vi /etc/NetworkManager/dispatcher.d/ifup-local
And append:
#!/bin/sh /bin/echo 1 > /proc/sys/net/ipv4/ip_forward /sbin/iptables -t nat -A POSTROUTING -s '10.0.0.0/16' -o eth0 -j MASQUERADE
The following command is required to make the script executable, otherwise it will not work:
chmod +x /etc/NetworkManager/dispatcher.d/ifup-local
-
On the client servers
CentOS Stream 8 / 9, Rocky Linux 8 / 9, and Fedora 36 / 37 additionally require:
yum remove hc-utils -y
This also goes for other methods to add a route to the OS.
Create the file:
vi /etc/NetworkManager/dispatcher.d/ifup-local
And append:
#!/bin/sh /sbin/ip route add default via 10.0.0.1
Finally, make it executable:
chmod +x /etc/NetworkManager/dispatcher.d/ifup-local
Step 5 - Adding nameservers
To add nameservers on the client server, edit the file /etc/systemd/resolved.conf
. In the section [Resolve]
, there should be the line #DNS=
. Un-comment this line by removing the #
and add some DNS servers or use the DNS servers by Hetzner:
DNS=185.12.64.2 185.12.64.1
Save the file and restart the server.
Step 6 - Cloud-init
If you don't want to setup the routes manually, you can use cloud-init and add the scripts below in the cloud config text box when you create new servers in Cloud Console.
Debian 10 / 11 / 12, and Ubuntu 18.04
-
NAT server
Replace
10.0.0.0/16
as needed.#cloud-config packages: - ifupdown package_update: true package_upgrade: true runcmd: - | cat <<'EOF' >> /etc/network/interfaces auto eth0 iface eth0 inet dhcp post-up echo 1 > /proc/sys/net/ipv4/ip_forward post-up iptables -t nat -A POSTROUTING -s '10.0.0.0/16' -o eth0 -j MASQUERADE EOF - reboot
-
Client server
Replace
10.0.0.1
as needed.#cloud-config packages: - ifupdown package_update: true package_upgrade: true runcmd: - | cat <<'EOF' >> /etc/network/interfaces auto ens10 iface ens10 inet dhcp post-up echo "Waiting..." post-up ip route add default via 10.0.0.1 EOF - reboot
Ubuntu 22.04 / 24.04
-
NAT server
Replace
10.0.0.0/16
as needed.#cloud-config package_update: true package_upgrade: true runcmd: - | cat <<'EOF' >> /etc/networkd-dispatcher/routable.d/10-eth0-post-up #!/bin/bash echo 1 > /proc/sys/net/ipv4/ip_forward iptables -t nat -A POSTROUTING -s '10.0.0.0/16' -o eth0 -j MASQUERADE EOF chmod +x /etc/networkd-dispatcher/routable.d/10-eth0-post-up - reboot
-
Client server
Replace
10.0.0.1
as needed.#cloud-config packages: - ifupdown package_update: true package_upgrade: true runcmd: - | cat <<'EOF' >> /etc/systemd/network/10-ens10.network [Match] Name=ens10 [Network] DHCP=yes Gateway=10.0.0.1 EOF - reboot
Ubuntu 20.04
-
NAT server
Replace
10.0.0.0/16
as needed.#cloud-config package_update: true package_upgrade: true runcmd: - | cat <<'EOF' >> /etc/networkd-dispatcher/routable.d/50-masq #!/bin/sh /bin/echo 1 > /proc/sys/net/ipv4/ip_forward /sbin/iptables -t nat -A POSTROUTING -s '10.0.0.0/16' -o eth0 -j MASQUERADE EOF - chmod +x /etc/networkd-dispatcher/routable.d/50-masq - reboot
-
Client server
Replace
10.0.0.1
as needed.#cloud-config package_update: true package_upgrade: true runcmd: - | cat <<'EOF' >> /etc/networkd-dispatcher/routable.d/50-masq #!/bin/sh /sbin/ip route add default via 10.0.0.1 EOF - chmod +x /etc/networkd-dispatcher/routable.d/50-masq - reboot
CentOS 7, CentOS Stream 8 / 9, Rocky Linux 8 / 9, Fedora 36 / 37
-
NAT server
Replace
10.0.0.0/16
as needed.#cloud-config packages: - iptables package_update: true package_upgrade: true runcmd: - | cat <<'EOF' >> /etc/NetworkManager/dispatcher.d/ifup-local #!/bin/sh /bin/echo 1 > /proc/sys/net/ipv4/ip_forward /sbin/iptables -t nat -A POSTROUTING -s '10.0.0.0/16' -o eth0 -j MASQUERADE EOF - chmod +x /etc/NetworkManager/dispatcher.d/ifup-local - reboot
-
Client server
Replace
10.0.0.1
as needed.#cloud-config package_update: true package_upgrade: true runcmd: - yum remove hc-utils -y - | cat <<'EOF' >> /etc/NetworkManager/dispatcher.d/ifup-local #!/bin/sh /sbin/ip route add default via 10.0.0.1 EOF - chmod +x /etc/NetworkManager/dispatcher.d/ifup-local - reboot
After the servers are created, you can use iptables -t nat -L
on the NAT server and ip route show
on the client server to check if the rules were added as expected.
Next, add the route mentioned in step 2 to your private Network and you're done!
Conclusion
If you followed all these steps, you have successfully configured your system to behave as a NAT router in your private Cloud Network.