IPv4 & IPv6 Leasing - Any RIR, Any LocationOrder Now
Hostperl

WireGuard Site-to-Site VPN on AlmaLinux and Rocky Linux

By Raman Kumar

Share:

Updated on Oct 20, 2026

WireGuard Site-to-Site VPN on AlmaLinux and Rocky Linux

Why this setup matters for production connectivity

A WireGuard site-to-site VPN gives you a clean way to connect two servers, two subnets, or a VPS and a private network without exposing internal services to the public internet. On Hostperl VPS and dedicated servers, that usually means one small tunnel for database replication, admin access, backup traffic, or private app-to-app communication.

This tutorial is limited to AlmaLinux and Rocky Linux. That is intentional: both use dnf, firewalld, and SELinux, so the workflow stays consistent on current RHEL-compatible hosting builds. If you are planning a new deployment on Hostperl, you can compare Hostperl VPS sizing with your network design before you begin.

By the end, you will have a working WireGuard site-to-site VPN with static tunnel IPs, firewall rules, routing, and a rollback path if you need to remove it later.

What you will build

You will create a tunnel between two Linux servers:

  • Site A: public IP 203.0.113.10, tunnel IP 10.44.0.1/24
  • Site B: public IP 203.0.113.20, tunnel IP 10.44.0.2/24

Replace these documentation IPs with your real server addresses. Keep the tunnel subnet private and unique. Do not reuse a subnet already in use on either side.

This layout works well for private service access, remote administration, and low-latency server-to-server traffic. It is not a full mesh design, and it does not replace a proper routed network plan on larger estates.

Prerequisites and safe starting point

Before you change anything, open one SSH session to each server and keep the original root session open until the new login is verified. If you already use a non-root administrator on your Hostperl server, use that account instead.

On your local computer

ssh root@203.0.113.10

That IP is a reserved documentation example. Replace it with the real public IP assigned to your server. Open a second terminal for the peer server as well.

On the VPS as root

cat /etc/os-release

This confirms you are on AlmaLinux or Rocky Linux before you continue. You should see a matching distribution name and version, such as AlmaLinux 9 or Rocky Linux 9.

Next, update both systems before you add the VPN packages.

On the VPS as root

dnf -y update

This applies security fixes and kernel updates. Reboot now if a kernel or networking package was updated, then reconnect before proceeding.

Install WireGuard and the tools you need

AlmaLinux and Rocky Linux ship WireGuard support through the standard repositories on current releases. Install the packages on both servers.

On the VPS as root

dnf -y install wireguard-tools firewalld

This installs the WireGuard command-line tools and makes sure the firewall service is available. If wireguard-tools is already present, dnf skips it cleanly.

Enable and start the firewall if it is not already active.

On the VPS as root

systemctl enable --now firewalld

Confirm both packages are available before you create keys.

On the VPS as root

wg --version

Expected output includes a WireGuard tools version string. If the command is missing, the package install did not complete.

Generate keys for each peer

WireGuard uses a private key and a public key on each server. Generate them separately on each side. Keep private keys readable only by root.

On the VPS as root

umask 077
wg genkey | tee /etc/wireguard/site-a.key | wg pubkey > /etc/wireguard/site-a.pub

Run the equivalent command on Site B, changing the filenames so you can identify the host later. The private key stays in /etc/wireguard; the public key can be shared.

Check the key files.

On the VPS as root

ls -l /etc/wireguard/*.key /etc/wireguard/*.pub

You should see root-only permissions on the private key and readable permissions on the public key. If the private key is too open, fix it now.

On the VPS as root

chmod 600 /etc/wireguard/site-a.key /etc/wireguard/site-b.key

Create the WireGuard configuration on Site A

Use wg-quick so the tunnel comes up as a systemd service. Site A will listen on UDP port 51820.

On the VPS as root

nano /etc/wireguard/wg0.conf

Paste this complete file for Site A, then save and exit with Ctrl+O, Enter, and Ctrl+X.

[Interface]
Address = 10.44.0.1/24
ListenPort = 51820
PrivateKey = REPLACE_WITH_SITE_A_PRIVATE_KEY
SaveConfig = false

[Peer]
PublicKey = REPLACE_WITH_SITE_B_PUBLIC_KEY
AllowedIPs = 10.44.0.2/32
Endpoint = 203.0.113.20:51820
PersistentKeepalive = 25

Replace the private key with the contents of /etc/wireguard/site-a.key and the peer public key with the value from Site B. Replace 203.0.113.20 with the public IP of the peer server.

Create the WireGuard configuration on Site B

Site B mirrors the tunnel with its own private key and endpoint.

On the VPS as root

nano /etc/wireguard/wg0.conf

Paste this file for Site B, then save and exit.

[Interface]
Address = 10.44.0.2/24
ListenPort = 51820
PrivateKey = REPLACE_WITH_SITE_B_PRIVATE_KEY
SaveConfig = false

[Peer]
PublicKey = REPLACE_WITH_SITE_A_PUBLIC_KEY
AllowedIPs = 10.44.0.1/32
Endpoint = 203.0.113.10:51820
PersistentKeepalive = 25

Replace the key values with the real contents from your servers. The endpoint on Site B must point at Site A’s public IP.

Harden permissions before starting the tunnel

WireGuard is sensitive to file permissions. Set them correctly before the service starts.

On the VPS as root

chmod 600 /etc/wireguard/wg0.conf
chown root:root /etc/wireguard/wg0.conf

Then inspect the file to confirm the placeholders are gone.

On the VPS as root

sed -n '1,200p' /etc/wireguard/wg0.conf

Do not continue until each server has its own private key and the peer public key is correct. A wrong key pair is the most common reason a tunnel never handshakes.

Open the firewall for WireGuard and allow forwarding

Firewalld needs an explicit UDP rule for the WireGuard port. Add it on both servers before bringing the interface up.

On the VPS as root

firewall-cmd --permanent --add-port=51820/udp
firewall-cmd --reload

This opens UDP 51820 permanently and reloads the active ruleset. Check that the port is listed.

On the VPS as root

firewall-cmd --list-ports

You should see 51820/udp in the output.

If you plan to route traffic between internal subnets through the tunnel, enable forwarding now. Add the kernel setting on both servers.

On the VPS as root

cat > /etc/sysctl.d/99-wireguard-forwarding.conf <<'EOF'
net.ipv4.ip_forward = 1
net.ipv6.conf.all.forwarding = 1
EOF
sysctl --system

This activates IPv4 and IPv6 forwarding. If you only need host-to-host access, forwarding is not strictly required, but keeping it enabled is useful for future routed services.

Bring up the tunnel and enable it at boot

Start the interface on both servers. Do not move on to routing until the service is active.

On the VPS as root

systemctl enable --now wg-quick@wg0

This starts the tunnel immediately and ensures it returns after reboot. Check status next.

On the VPS as root

systemctl status wg-quick@wg0 --no-pager

You want to see active (exited) for the service and no configuration errors in the log snippet.

Also check the interface and peer state.

On the VPS as root

wg show

A successful tunnel shows the peer public key, the endpoint, a latest handshake timestamp, and some traffic counters after you test it. If there is no handshake yet, continue to the ping test.

Test connectivity across the tunnel

Use ICMP first. It tells you whether the tunnel is up before you route any real service traffic through it.

On Site A as root

ping -c 4 10.44.0.2

On Site B as root

ping -c 4 10.44.0.1

Successful replies confirm the tunnel is working. If the ping fails, check keys, endpoints, and firewall rules before anything else.

For a more precise handshake check, run this on each server.

On the VPS as root

wg show wg0 latest-handshakes transfer

If the handshake time is recent and bytes are increasing, the tunnel is passing traffic. If the output shows zero transfer and no handshake, the most likely causes are a wrong public key, a blocked UDP port, or an incorrect endpoint IP.

Route a private subnet over the VPN

Many production uses need more than host-to-host reachability. The usual next step is to expose a private subnet behind one side of the tunnel. This example routes the 192.168.50.0/24 network behind Site B back to Site A.

First, tell Site A that the remote LAN lives behind Site B. Edit Site A’s peer block and extend AllowedIPs.

On the VPS as root

nano /etc/wireguard/wg0.conf

Change the Site A peer section to this:

[Peer]
PublicKey = REPLACE_WITH_SITE_B_PUBLIC_KEY
AllowedIPs = 10.44.0.2/32, 192.168.50.0/24
Endpoint = 203.0.113.20:51820
PersistentKeepalive = 25

Now add the return route on Site B if the remote network should reach resources behind Site A. Adjust the route to match your real subnet.

On the VPS as root

ip route add 192.168.60.0/24 dev wg0

That example assumes Site A owns 192.168.60.0/24. If your layout is different, replace it with the correct internal network. Add persistent routing through your normal network configuration system after testing the live route.

If you only need server-to-server access, you can stop here.

Keep SELinux in the safe path

On AlmaLinux and Rocky Linux, SELinux usually works with WireGuard without special policy changes. That said, if a service behind the tunnel still cannot connect, check whether SELinux is blocking the application rather than the VPN itself.

On the VPS as root

getenforce

If SELinux is enforcing, leave it that way unless you have a specific denial to solve. Do not disable SELinux just to make the tunnel feel easier. Use audit logs to diagnose a denial.

On the VPS as root

ausearch -m AVC,USER_AVC -ts recent

If you see denial entries tied to the application port or file access, fix the label or policy instead of turning SELinux off.

Rollback and recovery

If the tunnel causes a lockout or breaks your access path, keep the original SSH session open and remove the tunnel cleanly.

On the VPS as root

systemctl disable --now wg-quick@wg0

This stops WireGuard and prevents it from starting on the next boot.

Then remove the firewall rule if you no longer need it.

On the VPS as root

firewall-cmd --permanent --remove-port=51820/udp
firewall-cmd --reload

If you created a temporary sysctl file for forwarding and you no longer need it, remove it and reload sysctls.

On the VPS as root

rm -f /etc/sysctl.d/99-wireguard-forwarding.conf
sysctl --system

Finally, delete the config only after you are sure you do not need the tunnel.

On the VPS as root

rm -f /etc/wireguard/wg0.conf /etc/wireguard/site-a.key /etc/wireguard/site-a.pub

Apply the equivalent cleanup on Site B as well.

Troubleshooting the most likely failures

1. No handshake appears in wg show

Run this on both servers:

wg show
ss -lunp | grep 51820
firewall-cmd --list-ports

If you do not see the UDP socket, the service did not start correctly. If the port is missing from firewalld, reopen it. If the key pair is wrong, regenerate and replace the public keys.

2. Ping works one way but not the other

Check the peer AllowedIPs entries and confirm they are symmetrical for the tunnel IPs. A missing route or a wrong subnet in AllowedIPs is the usual clue.

3. The tunnel is up, but real services are unreachable

Test the service port directly over the tunnel.

curl -I http://10.44.0.2:8000

If ICMP works but the service does not, inspect the application firewall, SELinux denials, and any service binding that only listens on localhost.

4. The server loses public SSH access after enabling routes

Undo the last routing change first, not the entire tunnel. Keep the root session open on the console or existing SSH terminal while you revert the route. Then retest with ping and wg show.

Final verification and reboot test

Before you close the ticket or hand the server back to a customer, confirm the tunnel survives a reboot.

On the VPS as root

reboot

Reconnect after the host comes back online, then verify the service and the handshake again.

On the VPS as root

systemctl is-enabled wg-quick@wg0
systemctl status wg-quick@wg0 --no-pager
wg show

You should see the service enabled, active, and exchanging traffic. Run one final ping from each side and one real application test over the tunnel. If the tunnel is being used for a database or backup job, test that workload now, not later.

For Hostperl customers running private traffic between servers, this is the right time to document the peer keys, endpoint addresses, and allowed subnets in your handover notes. If you expect to build out more private connectivity later, pair this with a larger VPS or a dedicated server hosting plan when bandwidth, routing, or hardware isolation becomes part of the design.

If you need a private tunnel for backups, replication, or secure server-to-server traffic, Hostperl can help you plan the right VPS or dedicated server setup before you deploy. For customers who want stronger network isolation or higher throughput, Hostperl VPS and dedicated server hosting are the usual starting points.

That saves time during cutover and lowers the risk of breaking public access while you bring the tunnel online.

FAQ

Can I use this setup for IPv6?
Yes. Add an IPv6 tunnel address, enable forwarding, and extend the firewall and route rules on both sides. Keep the IPv4 setup working first so you have a stable fallback.

Should I expose WireGuard on a custom port?
You can, but 51820/UDP is the normal starting point. Change it only if your network policy requires a different port.

Do I need NAT for a simple two-server tunnel?
No. Host-to-host traffic does not need NAT. Add routing or NAT only when you want one side to reach a private LAN behind the other side.

What is the fastest way to tell whether the problem is firewall or keys?
Run wg show and ss -lunp | grep 51820. If the socket exists but there is no handshake, suspect keys or endpoint settings first. If the socket is missing or blocked, fix the firewall.

Can I disable root login after this?
Only after you have a verified non-root administrative path. Keep the original root session open until a second SSH session works and sudo has been tested.