How to Set Up OpenVPN Server on Fedora

Install the OpenVPN and easy-rsa packages via `dnf`, generate your certificates using the provided scripts, and configure the server daemon to listen on UDP port 1194 with appropriate SELinux and firewall rules.

You need a secure tunnel and the router won't cooperate

You are sitting in a coffee shop with an untrusted network. Your laptop needs to talk to a home server without exposing your credentials to the local Wi-Fi. You tried SSH tunneling, but it breaks when the connection drops. You need a persistent, encrypted tunnel that routes all traffic through a single endpoint. OpenVPN on Fedora handles this reliably, but the setup requires a few moving parts. You must generate a local certificate authority, configure a routing daemon, and tell the firewall how to masquerade traffic. Get the pieces in the right order and the tunnel stays up. Miss one step and the handshake fails silently.

How OpenVPN actually works on Fedora

OpenVPN does not just encrypt packets. It creates a virtual network interface, usually a TUN device, and sits between your application stack and the physical network. When you connect, the client and server negotiate a TLS handshake using certificates you generate locally. Once the tunnel is established, the server acts as a router. It receives encrypted UDP packets, decrypts them, forwards the payload to the destination network, and sends the response back through the same tunnel.

Think of the TUN interface as a virtual Ethernet cable that only exists inside the kernel. Applications send traffic to it exactly like they would send traffic to a physical NIC. The OpenVPN daemon intercepts those packets, wraps them in TLS, and ships them over UDP to the remote endpoint. The remote daemon unwraps them and injects them into its own virtual interface. Fedora makes this straightforward because the packages ship with easy-rsa for certificate management and integrate cleanly with firewalld and SELinux. You only need to wire the pieces together correctly.

Build the certificate authority

Every OpenVPN connection relies on a public key infrastructure. You will generate a root certificate authority, a server certificate signed by that authority, and client certificates. The server verifies the client, and the client verifies the server. This prevents man-in-the-middle attacks without relying on external certificate vendors.

Install the daemon and the certificate generation toolkit. Fedora bundles easy-rsa directly with the openvpn package, so you do not need to fetch it from GitHub.

sudo dnf install openvpn easy-rsa -y
# Install the daemon and the certificate management scripts in one transaction
sudo mkdir -p /etc/openvpn/easy-rsa
# Create a dedicated directory for your PKI workspace
cd /etc/openvpn/easy-rsa
cp -r /usr/share/easy-rsa/* .
# Copy the upstream scripts into your working directory so you can modify them safely

Initialize the PKI structure and generate the certificates. The easy-rsa scripts expect to run from the directory where you copied them. Run them sequentially. The nopass flag removes the passphrase requirement so the daemon can start automatically without a human typing a password.

./easyrsa init-pki
# Create the public key infrastructure directory tree and default configuration
./easyrsa build-ca nopass
# Generate the root CA certificate and private key without a passphrase
./easyrsa build-server-full server nopass
# Create a server certificate and key, signed by the CA, with no passphrase
./easyrsa build-client-full client1 nopass
# Generate a client certificate and key for your first device
./easyrsa gen-crl
# Build a certificate revocation list so you can disable compromised clients later

The scripts will prompt you for a Common Name. Press Enter to accept the defaults unless you have a strict naming convention. The generated files live inside pki/. You need to copy the public certificates and private keys to a location where the OpenVPN daemon can read them. The daemon runs as a restricted user, so the paths must be accessible but secure.

sudo mkdir -p /etc/openvpn/easy-rsa/pki
# Ensure the PKI directory exists before copying
sudo cp /etc/openvpn/easy-rsa/pki/ca.crt /etc/openvpn/ca.crt
# Copy the CA public certificate to the main OpenVPN directory
sudo cp /etc/openvpn/easy-rsa/pki/issued/server.crt /etc/openvpn/server.crt
# Move the server public certificate to the configuration root
sudo cp /etc/openvpn/easy-rsa/pki/private/server.key /etc/openvpn/server.key
# Place the server private key where the daemon can read it
sudo cp /etc/openvpn/easy-rsa/pki/crl.pem /etc/openvpn/crl.pem
# Copy the revocation list so the server can check client validity

Keep the private keys restricted. Run sudo chmod 600 /etc/openvpn/server.key to prevent other users from reading them. OpenVPN will refuse to start if the key permissions are too open. The daemon checks file permissions before binding to the port and aborts immediately if it detects world-readable secrets.

Generate the certificates once. Rebuild the CA only if the private key leaks.

Configure the server daemon

The server configuration file tells OpenVPN how to behave. You will define the network interface, the routing table, DNS push options, and security parameters. Fedora uses a drop-in configuration system, so you place your file in /etc/openvpn/ and the service manager handles the rest.

Create the main server configuration. This example routes all client traffic through the VPN, which is the most common setup for remote access.

sudo tee /etc/openvpn/server.conf <<EOF
port 1194
# Listen on the standard OpenVPN UDP port
proto udp
# Use UDP for lower latency and better performance with NAT traversal
dev tun
# Create a Layer 3 virtual network interface instead of a Layer 2 bridge
ca /etc/openvpn/ca.crt
# Path to the root CA certificate for verifying clients
cert /etc/openvpn/server.crt
# Path to the server's public certificate
key /etc/openvpn/server.key
# Path to the server's private key
dh /etc/openvpn/easy-rsa/pki/dh.pem
# Diffie-Hellman parameters for the initial key exchange
server 10.8.0.0 255.255.255.0
# Assign the virtual subnet and netmask to the TUN interface
ifconfig-pool-persist ipp.txt
# Track client IP assignments so they get the same address on reconnect
push "redirect-gateway def1 bypass-dhcp"
# Route all client traffic through the VPN tunnel
push "dhcp-option DNS 1.1.1.1"
# Push a public DNS resolver to clients instead of Google's deprecated defaults
push "dhcp-option DNS 1.0.0.1"
# Secondary DNS resolver for redundancy
keepalive 10 120
# Send a ping every 10 seconds and assume dead after 120 seconds of silence
user nobody
# Drop root privileges after binding to the port
group nobody
# Run the worker processes under the unprivileged nobody group
persist-key
# Keep the private key in memory across restarts to avoid re-reading files
persist-tun
# Preserve the TUN interface state during daemon restarts
status openvpn-status.log
# Write connection statistics to a log file in the working directory
verb 3
# Set logging verbosity to a moderate level for troubleshooting
EOF

Notice the push directives. They modify the client's routing table automatically when the connection establishes. You do not need to configure static routes on every laptop. The server handles it. Also, avoid editing files in /usr/lib/ or /usr/share/. Those paths ship with the package and get overwritten on updates. Always work in /etc/. Fedora's packaging model treats /etc/ as the user-modified layer and /usr/ as the immutable package layer. Respect that boundary or your changes will vanish on the next dnf upgrade --refresh.

Test the configuration syntax before starting the daemon. Run openvpn --config /etc/openvpn/server.conf --test-config to catch typos early.

Enable routing and open the port

OpenVPN creates a virtual interface, but the kernel does not forward packets between interfaces by default. You must enable IP forwarding and tell firewalld to masquerade the traffic. SELinux also needs explicit permission to allow the daemon to manipulate network rules.

Enable IPv4 forwarding permanently. The runtime setting takes effect immediately, but the configuration file ensures it survives reboots.

sudo sysctl -w net.ipv4.ip_forward=1
# Enable packet forwarding in the kernel right now
echo "net.ipv4.ip_forward=1" | sudo tee -a /etc/sysctl.d/99-openvpn.conf
# Persist the setting across reboots using a drop-in sysctl configuration

Configure the firewall. firewalld uses zones and services. You need to open the UDP port and enable masquerading so the server can translate client IP addresses to its own public IP. Masquerading is just dynamic NAT. It rewrites the source IP of outgoing packets so the internet sees traffic coming from the server instead of the virtual 10.8.0.0 subnet.

sudo firewall-cmd --permanent --add-port=1194/udp
# Allow incoming OpenVPN traffic in the permanent zone configuration
sudo firewall-cmd --permanent --add-masquerade
# Enable NAT so client traffic can reach the internet through the server
sudo firewall-cmd --reload
# Apply the permanent changes to the running firewall without dropping connections

SELinux blocks unprivileged daemons from modifying network rules by default. You must flip the relevant booleans. The -P flag writes the change to disk so it persists after a reboot.

sudo setsebool -P openvpn_use_network 1
# Allow the OpenVPN domain to bind to network sockets and manage connections
sudo setsebool -P openvpn_manage_dhcp 1
# Permit the daemon to push DHCP options to clients through the tunnel

Run firewall-cmd --reload after every rule change. Otherwise the runtime configuration and the persistent configuration diverge, and you will spend hours debugging why a port appears open in --list-all but drops packets in reality.

Reload the firewall before starting the service. A closed port will silently drop the TLS handshake.

Start the service and verify

Fedora uses systemd instance templates for OpenVPN. The service name follows the pattern openvpn-server@<config-name>. Since your configuration file is named server.conf, the instance is openvpn-server@server. This template pattern lets you run multiple OpenVPN servers on the same machine by simply naming your configuration files differently.

Enable and start the daemon.

sudo systemctl enable --now openvpn-server@server
# Create the systemd symlink and start the service immediately
sudo systemctl status openvpn-server@server
# Check the active state and recent log lines in one view

Verify the tunnel is routing correctly. Check the virtual interface and the routing table.

ip addr show tun0
# Confirm the TUN interface exists and has an IP from the 10.8.0.0 subnet
ip route show table all | grep 10.8.0.0
# Verify the kernel knows how to route traffic to the VPN subnet

If the service fails, check the journal. Run journalctl -xeu openvpn-server@server to see the exact failure point. The x flag adds explanatory context and the e flag jumps to the end of the log. Most startup failures are permission issues on the private key or a missing certificate path. Always check systemctl status before restart. The status output shows the last few log lines and the current state in a single view.

Run journalctl -xe first. Read the actual error before guessing.

Common pitfalls and what the error looks like

The setup fails in predictable ways. Recognizing the exact error string saves time.

If the service refuses to start, you will see Options error: --key fails with 'Access denied' in the journal. This means the private key file is readable by other users. Fix it with sudo chmod 600 /etc/openvpn/server.key.

If clients connect but cannot reach the internet, check masquerading. The journal will show route addition failed using ip route: File exists. This usually means the routing table already contains the subnet from a previous failed start. Flush the interface with sudo ip link del tun0 and restart the service.

SELinux denials show up in journalctl -t setroubleshoot with a one-line summary. Do not disable SELinux to fix OpenVPN. Flip the boolean instead. A missing boolean produces Permission denied errors when the daemon tries to push DHCP options or modify routing tables.

If firewall-cmd --list-all shows the port but packets still drop, verify the active zone. Run firewall-cmd --get-active-zones. If your interface is in public but you added the rule to work, the traffic never matches. Add the rule to the correct zone or change the interface zone.

Trust the package manager. Manual file edits drift, snapshots stay.

When to use OpenVPN versus alternatives

Use OpenVPN when you need a mature, widely supported VPN daemon that handles complex routing and firewall traversal reliably. Use WireGuard when you want a simpler configuration with lower overhead and modern cryptography. Use SSH tunneling when you only need to proxy a single port or run a quick ad-hoc connection. Use a commercial VPN client when you do not want to manage certificates or server infrastructure yourself. Stay on the upstream OpenVPN package if you only need standard TLS-based tunneling without custom patches.

Pick the tool that matches your threat model. Simpler stacks break less.

Where to go next