The next recipe works on CentOS 7 as for beginning of 2025 year.
- yum install openvpn easy-rsa
- cd /etc/openvpn
- /usr/share/easy-rsa/3.0.8/easyrsa init-pki
- /usr/share/easy-rsa/3.0.8/easyrsa build-ca nopass
- /usr/share/easy-rsa/3.0.8/easyrsa --days=365 build-server-full server nopass
- /usr/share/easy-rsa/3.0.8/easyrsa --days=365 build-client-full client1 nopass
- /usr/share/easy-rsa/3.0.8/easyrsa --days=365 build-client-full client2 nopass
- openssl dhparam -out /etc/openvpn/server/dh.pem 2048
- Create config-file /etc/openvpn/server.conf:
port 1194 proto udp dev tun ca /etc/openvpn/server/pki/ca.crt cert /etc/openvpn/server/pki/issued/server.crt key /etc/openvpn/server/pki/private/server.key dh /etc/openvpn/server/dh.pem server 10.8.0.0 255.255.255.0 ifconfig-pool-persist ipp.txt push "redirect-gateway def1" push "dhcp-option DNS 8.8.8.8" push "dhcp-option DNS 8.8.4.4" keepalive 10 120 cipher AES-256-CBC user openvpn group openvpn persist-key persist-tun status /var/log/openvpn-server-status.log log-append /var/log/openvpn.log verb 3
- Create .ovpn file for client:
client dev tun proto udp remote vpn-server.host.name 1194 udp resolv-retry infinite nobind persist-key persist-tun cipher AES-256-CBC verb 3 <cert> -----BEGIN CERTIFICATE----- // client base64-encoded cert goes here -----END CERTIFICATE----- </cert> <key> -----BEGIN PRIVATE KEY----- // client base64-encoded private key goes here -----END PRIVATE KEY----- </key> <ca> -----BEGIN CERTIFICATE----- // ca base64-encoded cert goes here -----END CERTIFICATE----- </ca>
Or, alternatively, if you don't want to embed certs and key to the .ovpn config file itself, you can reference them with these 3 lines:
ca ca.crt cert client1.crt key client1.key
- Open UDP port 1194 in the server's firewall.
- Setup NAT and forwarding if needed.
- To automatically start server using systemd place server config file to the directory /etc/openvpn/server and name it accordingly, for example udp1194.conf. Then the command should be:
systemctl enable openvpn-server@udp9997 --now
What is the difference between tun and tap devices which openvpn provides?
They're two different kernel interface types from the same tun module, and the distinction is what layer of the OSI stack the tunnel carries.
tun — layer 3, point-to-point. Carries bare IP packets. No Ethernet header, no MAC addresses, no ARP, no broadcast domain. The interface has no L2 identity at all — ip link show tun0 shows link/none.
tap — layer 2, virtual Ethernet. Carries full Ethernet frames. The interface has a MAC address, participates in ARP, and can be enslaved to a bridge alongside a physical NIC, putting remote clients on the same broadcast domain as the LAN.
| tun | tap | |
|---|---|---|
| Encapsulates | IP packets | Ethernet frames |
| Per-packet overhead | none | extra +14 bytes (Ethernet header) |
| Broadcast/multicast | not carried | carried, floods to all clients |
| Non-IP protocols | no | yes (IPX, AppleTalk, STP, raw ARP) |
| DHCP from LAN server | no (OpenVPN assigns via push) | yes, client can DHCP off the real LAN |
| Bridging to physical NIC | no | yes |
| Android / iOS clients | supported | not supported |
| OpenVPN DCO | supported | not supported |
| Scales to many clients | yes | poorly |
Why tun is the default answer
The tap cost is mostly invisible until you have clients: every ARP request, every DHCP discover, every Windows NetBIOS/mDNS/SSDP broadcast on the LAN gets replicated and pushed down every single tunnel. On a link with 20 remote clients over consumer uplinks that's a constant background load doing nothing useful, and it's why bridged setups feel fine at 2 clients and terrible at 30.
Additional practical constraints:
- Mobile clients can't do tap at all. The Android and iOS VPN APIs only expose an L3 interface. If any client is a phone, the decision is already made.
- DCO rules out tap. If you're using ovpn-dco / ovpn kernel offload on Rocky or Fedora for throughput, it's L3-only. Configuring dev tap silently falls back to userspace tunneling (or errors, depending on version), losing the reason you enabled it.
- tap on Windows requires the separate tap-windows6 driver; the newer dco-win/wintun path doesn't cover it.
When tap is right choice
- Non-IP protocols on the wire, or legacy software that discovers peers only via L2 broadcast.
- Windows browsing/discovery that must work without a WINS or mDNS repeater.
- Wake-on-LAN over the tunnel, or anything needing raw frames.
- Small site-to-site links (2–3 endpoints) where flattening the L2 domain saves a lot of routing config.
How to avoid default route replacement on client after VPN-connection initializing?
Fix on the server (affects all clients)
Just don't push it:
# /etc/openvpn/server/server.conf — remove or comment #push "redirect-gateway def1 bypass-dhcp"
and push only the routes to subnets that should go through the tunnel, /etc/openvpn/server/server.conf file should contain:
push "route 10.10.3.0 255.255.255.0" push "route 192.168.50.0 255.255.255.0"
Or it can be done per-client instead of globally, via client-config-dir:
# server.conf: client-config-dir /etc/openvpn/server/ccd
# /etc/openvpn/server/ccd/<common-name> — must match the client's cert CN exactly push "route 10.10.3.0 255.255.255.0"
Note ccd files can't un-push a global push "redirect-gateway ..." — a global push always applies. If some clients need full tunnel and others don't, move redirect-gateway out of the global section entirely and push it only from the ccd files for specific clients that want it.
Fix on the client (when you can't change the server, for example)
The surgical option — drop just that one directive, keep everything else including DNS and routes:
# client.ovpn pull-filter ignore "redirect-gateway" pull-filter ignore "dhcp-option DNS" # If you need to drop the pushed DNS as well pull-filter ignore "dhcp-option DOMAIN" # If you need to drop the pushed DNS as well pull-filter ignore "block-outside-dns" # If you need to drop the pushed DNS as well
pull-filter uses a prefix match on the whole option string, evaluated in the order the filters appear. So:
pull-filter ignore "route" # ALSO kills route-gateway and route-metric → breaks tunnel routing pull-filter ignore "route " # Note trailing space: only actual "route x.x.x.x" lines
The blunt option — ignore all pushed routes and DHCP options. Note, that route-nopull kwyword also discards pushed dhcp-option (DNS, DOMAIN, block-outside-dns), so you must re-add anything you still need locally:
# client.ovpn route-nopull route 10.10.3.0 255.255.255.0 route 192.168.50.0 255.255.255.0 dhcp-option DNS 10.10.3.10
Third option — receive routes but install them yourself in a script:
# client.ovpn route-noexec up /etc/openvpn/my-routes.sh
route-noexec still populates $route_network_1, $route_gateway_1, $ifconfig_local etc. In the my-routes.sh script environment, so you can install selectively. Useful when you want the pushed list but with your own metrics or table.
0 Responses
Stay in touch with the conversation, subscribe to the RSS feed for comments on this post.
You must be logged in to post a comment.