Set Up a Secure OpenVPN Server on AlmaLinux 9 for China

You have users in mainland China who need to reach a service that is blocked, geo-restricted, or simply slow to reach from behind the GFW. The first thing you will learn is that a generic UDP-based OpenVPN config gets killed within hours. What survives is a TCP 443 setup on an AlmaLinux 9 VPS in Vietnam, with certificate auth, a locked-down firewall, and a reconnection test that mirrors how real mobile networks behave. This guide builds exactly that, step by step, with commands you can paste and run.
- Key takeaways: run OpenVPN on TCP 443 to blend with HTTPS traffic.
- Use ECC certificates (secp384r1) for faster handshakes, not RSA 2048.
- Lock the firewall to the VPN port, SSH, and the OpenVPN management interface only.
- A 30-minute disconnect/reconnect test tells you more than any config review.
Prerequisites
- An AlmaLinux 9 VPS with root or a sudo user. If you need one, a Linux VPS with a dedicated IPv4 from the Vietnam range works well for this.
- A domain name pointing to the VPS IP, or a plan to use the raw IP. A domain makes the cert setup cleaner but is not mandatory.
- OpenVPN client software on the machine you test from. On Windows, the official OpenVPN GUI; on Android, the OpenVPN Connect app.
- Basic familiarity with
dnf,systemctl, and editing files under/etc/.
Why TCP 443 and ECC Matter for China Access
OpenVPN over UDP on port 1194 is the default in most tutorials. Behind the GFW, that traffic is identified quickly and throttled or reset. TCP on port 443 is the same port HTTPS uses, so the traffic stream looks like ordinary web traffic to middleboxes, though it is not encrypted as TLS by default, which is why you add the tls-crypt key later. This is a practical trade-off, not a guarantee, but in practice it survives far longer.
ECC certificates matter for a different reason. A handshake over a high-latency link is dominated by round trips, not CPU time. secp384r1 keys are smaller than RSA 2048, so the TLS handshake ships fewer bytes. On a mobile connection in China with 150-250 ms RTT to Vietnam, that saving is visible on every reconnect.
Step 1 - Installing OpenVPN and Easy-RSA on AlmaLinux 9
AlmaLinux 9 ships OpenVPN 2.5 and Easy-RSA 3.x in the EPEL repository. Enable EPEL first, then install both packages.
dnf install -y epel-release
dnf install -y openvpn easy-rsa firewalld
Verify the install and check the OpenVPN version:
openvpn --version | head -n 1
Expected output starts with OpenVPN 2.5.x or newer. If firewalld was already running, leave it, you reconfigure it in Step 4.
Step 2 - Building the PKI with Easy-RSA
Copy the Easy-RSA samples to a working directory under /etc/openvpn. Do not run this as root in your home directory, the permissions get messy.
mkdir -p /etc/openvpn/easy-rsa
cp -r /usr/share/easy-rsa/3/* /etc/openvpn/easy-rsa/
cd /etc/openvpn/easy-rsa
Edit the vars file to set ECC and your key size. Open vars and set:
set_var EASYRSA_ALGO "ec"
set_var EASYRSA_CURVE "secp384r1"
Then initialize the PKI and build the CA. The CA passphrase is important, write it down somewhere safe.
./easyrsa init-pki
./easyrsa build-ca nopass
Now generate the server key pair and the Diffie-Hellman parameters, though with ECC the DH file is not strictly needed, keep it for compatibility with older clients:
./easyrsa gen-req server nopass
./easyrsa sign-req server server
./easyrsa gen-dh
Generate the tls-crypt key, this protects against port scanning and DoS on the OpenVPN port:
openvpn --genkey secret /etc/openvpn/ta.key
Verify the PKI tree exists:
ls -l /etc/openvpn/easy-rsa/pki/issued/
You should see server.crt. If you see nothing, the sign request failed and you must rerun the gen-req and sign-req pair.
Step 3 - Writing the OpenVPN Server Configuration
Create /etc/openvpn/server.conf. This is the file that makes or breaks the China deployment, so pay attention to every line.
port 443
proto tcp
dev tun
ca /etc/openvpn/easy-rsa/pki/ca.crt
cert /etc/openvpn/easy-rsa/pki/issued/server.crt
key /etc/openvpn/easy-rsa/pki/private/server.key
dh /etc/openvpn/easy-rsa/pki/dh.pem
tls-crypt /etc/openvpn/ta.key
server 10.8.0.0 255.255.255.0
ifconfig-pool-persist ipp.txt
push "redirect-gateway def1 bypass-dhcp"
push "dhcp-option DNS 1.1.1.1"
push "dhcp-option DNS 8.8.8.8"
keepalive 10 60
cipher AES-256-GCM
data-ciphers AES-256-GCM
auth SHA256
user nobody
group nobody
persist-key
persist-tun
status /var/log/openvpn-status.log
log-append /var/log/openvpn.log
verb 3
mute 20
explicit-exit-notify 0
max-clients 20
The proto tcp and port 443 pair is the core of the survival strategy. The tls-crypt line hides the fact that this is OpenVPN at all, a scanner sees a TLS-looking handshake and moves on. explicit-exit-notify 0 is mandatory for TCP, the option only applies to UDP and causes a startup warning if left at its default.
Start the service and enable it on boot:
systemctl enable --now openvpn@server
systemctl status openvpn@server
Expected output: active (running). If it failed, read the log:
journalctl -u openvpn@server -n 30
Step 4 - Firewalld Rules and IP Forwarding
AlmaLinux 9 uses firewalld, not ufw. The default zone is usually public. Open the VPN port and SSH, then make the firewall aware of the tun interface.
firewall-cmd --permanent --add-port=443/tcp
firewall-cmd --permanent --add-service=ssh
firewall-cmd --permanent --add-interface=tun0
firewall-cmd --reload
Now enable IP forwarding in the kernel. Edit /etc/sysctl.conf and add or change:
net.ipv4.ip_forward = 1
Apply it immediately:
sysctl -p
Verify forwarding is active:
sysctl net.ipv4.ip_forward
Expected output: net.ipv4.ip_forward = 1. The last step is the NAT masquerade, this is what lets VPN clients reach the internet through the VPS IP:
firewall-cmd --permanent --direct --add-rule ipv4 nat POSTROUTING 0 -s 10.8.0.0/24 -o eth0 -j MASQUERADE
firewall-cmd --reload
Replace eth0 with your actual interface name, check with ip link if unsure. On many VPS images it is eth0, but some use ens3 or similar.
Step 5 - Generating a Client Certificate and Config
Generate a client key pair on the server, then sign it. Do this for every device that connects.
cd /etc/openvpn/easy-rsa
./easyrsa gen-req client1 nopass
./easyrsa sign-req client client1
The client profile needs the CA cert, the client cert, the client key, and the tls-crypt key. The cleanest way is to inline them all into one .ovpn file. Create client1.ovpn with this structure, replacing the file paths with the actual content:
client
dev tun
proto tcp
remote your-vps-ip 443
resolv-retry infinite
nobind
persist-key
persist-tun
remote-cert-tls server
cipher AES-256-GCM
data-ciphers AES-256-GCM
auth SHA256
verb 3
<ca>
(content of /etc/openvpn/easy-rsa/pki/ca.crt)
</ca>
<cert>
(content of /etc/openvpn/easy-rsa/pki/issued/client1.crt)
</cert>
<key>
(content of /etc/openvpn/easy-rsa/pki/private/client1.key)
</key>
<tls-crypt>
(content of /etc/openvpn/ta.key)
</tls-crypt>
Import this file into the OpenVPN client on the test machine. The remote-cert-tls server line is not optional, it prevents a man-in-the-middle attack where a rogue client cert pretends to be the server.
Step 6 - Testing the Connection and the 30-Minute Reconnect Test
Connect from your client. On success you get an IP in the 10.8.0.0/24 range. Verify from the client side that traffic actually routes through the VPN:
curl ifconfig.me
The output must show the Vietnam VPS IP, not your local IP. If it shows your local IP, the redirect-gateway push is not being applied, which usually means the client config is missing the redirect-gateway def1 line or the server is not pushing it.
Now the test that matters. On a mobile network, disconnect the client, wait 30 minutes, and reconnect. The GFW is most aggressive with connections that reset and immediately retry with the same fingerprint. A config that reconnects cleanly after a long idle period is one that will hold up in daily use. Run this test three times from different locations if you can. A single clean reconnect from your office tells you very little.
Check the server log for accepted connections:
grep "client1" /var/log/openvpn.log | tail -n 10
You want to see Incoming Data Channel: TLS 1.3 or similar, confirming the ECC handshake completed.
Troubleshooting Common OpenVPN Failures
Client connects but no internet traffic flows. Check the masquerade rule and IP forwarding. Both must be active. Run sysctl net.ipv4.ip_forward and firewall-cmd --direct --get-all-rules. The most common cause is the wrong interface name in the masquerade rule, or the firewall reload wiped the direct rule because it was not added with --permanent.
Connection is reset within seconds of connecting. This is the GFW fingerprinting the OpenVPN handshake. Confirm tls-crypt is in the server config and in the client .ovpn file. If you are testing from a network that already blocks the target service at the IP level, try a different test location.
Server fails to start with a TLS error. The tls-crypt key file is missing or the server cert and key do not match. Regenerate the key with openvpn --genkey secret, and verify the cert matches the key with openssl x509 -noout -modulus -in server.crt | openssl md5 versus the same on the key file. They must be identical.
Log shows explicit-exit-notify warning. You left the option as its default on a TCP config. Set it to 0 explicitly, as in the config above, and restart.
FAQ
Is TCP 443 OpenVPN really undetectable in China?
No, nothing is undetectable. TCP 443 with tls-crypt makes the traffic look like HTTPS to passive scanners, which survives most automated blocking. Deep packet inspection that actively probes the TLS handshake can still identify it. This setup is a practical survival strategy, not a guarantee.
Why use ECC certificates instead of RSA?
ECC keys are smaller, so the TLS handshake ships fewer bytes. On high-latency links, which is what a connection from China to Vietnam is, the handshake is round-trip bound, not CPU bound. secp384r1 is a good balance between speed and security margin.
Should I use a domain name or the raw IP for the connection?
Either works. A domain makes it easier to change the VPS IP later without redistributing client configs, but it also adds DNS resolution to the connection path, which can occasionally be tampered with. For maximum resilience, use the raw IP and update client configs when the IP changes.
How many clients can this single VPS handle?
The max-clients 20 limit in the config is conservative. A 2GB RAM VPS with one vCPU handles 10-15 concurrent OpenVPN clients comfortably for browsing and API access. Heavy video streaming will saturate the CPU faster than the RAM. For that workload, consider a larger Linux VPS or a dedicated server.
Related articles
- Set up a WireGuard VPN on your VPS in 10 minutes
- Install OpenVPN on a VPS at thueVPS, step by step guide
- Set up a VPS VPN for China users on Ubuntu 24.04
- Secure an AlmaLinux 9 VPS with SELinux and firewalld
越南VPS搭建OpenVPN访问中国要点
本文讲解了如何在 AlmaLinux 9 越南 VPS 上搭建 OpenVPN 服务器,使用 TCP 443 端口和 ECC 证书提高抗封锁能力。关键配置包括启用 tls-crypt 隐藏协议特征、配置 firewalld 防火墙和 NAT 转发,并完成 30 分钟重连测试验证稳定性。建议使用带越南本地 IPv4 的 VPS 部署,中国大陆用户连接延迟更低,连接也更稳定。


