Optimize Nginx on AlmaLinux VPS in Vietnam for Global Low Latency

A server in Vietnam serving users in Singapore, Japan, Europe, or the US has a hard physical floor: light to Singapore is around 30-40 ms round trip, to Western Europe often 150-200 ms, to the US West Coast 180-250 ms. No Nginx config removes that. What you can remove is the overhead your stack adds on top of the network. This guide is about getting a Nginx AlmaLinux VPS Vietnam low latency setup that stops wasting milliseconds on the TCP stack, on keepalive handling, on TLS negotiation, and on upstream choices. Everything here runs on AlmaLinux 9, tested against a standard Linux VPS with full root access.
Prerequisites
- An AlmaLinux 9 VPS (9.4 or newer) with root or a sudo user.
- Nginx installed from the official nginx.org repo or EPEL. This guide assumes nginx 1.26+.
- A domain pointing to the server if you test TLS; not strictly needed for TCP tuning.
- Outbound ICMP or TCP reachability to a few global endpoints for latency testing.
Why your Vietnam VPS feels slow to global users
When a user in Paris loads a page from a server in Hanoi or Ho Chi Minh City, the traffic crosses Vietnam domestic networks, then international transit through submarine cables, then European backbones. The routing is decided by your provider's upstreams, not by Nginx. But several factors sit squarely in your control: the TCP congestion control algorithm, the send and receive buffer sizes, and whether the connection survives long enough to be reused.
The default Linux TCP stack on AlmaLinux is conservative. It assumes a shared, lossy network. On a modern VPS with a dedicated IPv4 and NVMe storage, that conservatism costs you. TCP BBR changes the model: instead of using packet loss as the congestion signal, BBR estimates the bottleneck bandwidth and round-trip time directly. On long, high-latency paths from Vietnam to Europe or the US, BBR frequently delivers noticeably higher throughput and lower latency under load than the default cubic algorithm.
The second hidden cost is connection setup. Every new TLS connection from a distant user costs one full round trip for the TCP handshake and another one or two for TLS. If your Nginx closes keepalive connections too early, a user loading 20 assets pays that round-trip tax 20 times. Tuning keepalive and TLS session resumption is where Nginx itself earns its keep.
越南 VPS 的延迟瓶颈通常在网络路径而非 Nginx 配置。
On a Vietnam VPS the latency bottleneck is usually the network path, not the Nginx configuration.
Step 1: Enable TCP BBR and raise socket buffers
BBR is built into the AlmaLinux 9 kernel, so there is no module to compile. You only need to switch the default congestion control and make sure the TCP buffers allow it to breathe.
Check what is available and what is active:
sysctl net.ipv4.tcp_congestion_control
sysctl net.ipv4.tcp_available_congestion_control
Expected output shows cubic active and bbr in the available list. If you do not see bbr in the available list, your kernel is too old. On AlmaLinux 9 with kernel 5.14+, BBR is present.
Create a sysctl drop-in file, do not edit /etc/sysctl.conf directly because package updates can overwrite it:
cat > /etc/sysctl.d/99-network-performance.conf <<'EOF'
net.core.default_qdisc = fq
net.ipv4.tcp_congestion_control = bbr
net.ipv4.tcp_fastopen = 3
net.ipv4.tcp_slow_start_after_idle = 0
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
EOF
sysctl --system
What each line does: fq is the fair queueing qdisc that BBR expects for pacing. tcp_fastopen = 3 enables TFO for both client and server roles, which can save one round trip on repeat connections from supported clients. tcp_slow_start_after_idle = 0 stops the stack from resetting the congestion window when a connection goes quiet, which matters for keepalive connections. The rmem_max and wmem_max values let the buffers scale to 16 MB when the path needs it.
Verify:
sysctl net.ipv4.tcp_congestion_control
# expected: bbr
Step 2: Match Nginx worker processes and connections to the VM
Nginx configuration on a VPS is about matching the hardware. Do not blindly copy a guide written for a 32-core bare-metal box.
Set worker_processes to the number of vCPUs, and worker_connections high enough to hold all keepalive connections. The key subtlety: keepalive connections from global users are idle most of the time, so they are cheap, but each one occupies a file descriptor. If you under-provision worker_connections, Nginx starts rejecting new connections under load.
# /etc/nginx/nginx.conf, inside the main context
worker_processes auto;
worker_rlimit_nofile 65535;
events {
worker_connections 8192;
use epoll;
multi_accept on;
}
worker_rlimit_nofile raises the per-worker file descriptor limit, otherwise the kernel default of 1024 caps you at roughly 1024 concurrent connections per worker regardless of worker_connections.
Verify the number of worker processes matches your vCPUs:
ps -eo pid,comm | grep nginx
nproc
Step 3: Keepalive and TLS session tuning in the http block
This is where most of the real-world latency win lives. A distant user's first request cannot be faster than the physical round trip, but the 20 subsequent asset requests can be nearly free if the connection stays open and the TLS session is resumed.
In the http block of /etc/nginx/nginx.conf:
keepalive_timeout 30s;
keepalive_requests 1000;
send_timeout 10s;
reset_timedout_connection on;
client_body_timeout 10s;
# TLS session resumption
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off;
Set keepalive_timeout to 30 seconds. Long enough that a user moving between pages reuses the connection. But do not set it to 300 seconds: idle keepalive connections consume memory and file descriptors on a small VPS, and 30 seconds is a sane middle ground. keepalive_requests 1000 allows many requests per connection so a page with 50 assets does not force a new TLS handshake midway.
Session tickets: keep them off if you have a single server, because the shared cache works fine and tickets add a theoretical forward-secrecy concern. On a multi-server setup behind a load balancer you would need shared ticket keys, which is more moving parts than most VPS setups need.
Verify TLS session reuse from a client outside Vietnam, for example from your local machine:
curl -svI https://your-domain.com 2>&1 | grep -i "SSL-Session"
# Look for "Reused: No" on the first request, then run it again:
curl -svI https://your-domain.com 2>&1 | grep -i "SSL-Session"
# Look for "Reused: Yes"
Step 4: Enable HTTP/3 and Brotli for distant users
HTTP/3 runs over QUIC, which uses UDP. This matters more for global users than for domestic Vietnamese users, because QUIC avoids head-of-line blocking on lossy long-distance paths. If a packet is dropped on the Vietnam-to-Europe leg, HTTP/2 stalls the whole connection until that packet is retransmitted. QUIC only stalls the single stream that lost the packet.
On AlmaLinux 9, the nginx package from EPEL or nginx.org includes the HTTP/3 module (dynamic or built-in depending on the build). Add a listen directive with the quic parameter on the TLS server block:
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
# HTTP/3 over UDP
listen 443 quic reuseport;
listen [::]:443 quic reuseport;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_certificate /etc/nginx/ssl/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/privkey.pem;
add_header Alt-Svc 'h3=":443"; ma=86400' always;
add_header quic $http3 always;
}
The Alt-Svc header tells browsers that HTTP/3 is available on port 443. The reuseport parameter allows multiple workers to accept QUIC packets on the same UDP socket.
Brotli compression is also worth enabling for global users. gzip at level 6 has been the default for a decade, but Brotli at quality 5 typically compresses text assets 10-20% better, and smaller responses mean fewer round trips over a high-latency path. Install the module and enable it:
dnf install nginx-mod-http-brotli
# In the http block
brotli on;
brotli_comp_level 5;
brotli_static on;
brotli_types text/plain text/css application/javascript application/json image/svg+xml application/xml+rss;
Verify HTTP/3 is served:
curl --http3 -I https://your-domain.com 2>&1 | head -5
# If curl lacks --http3 support, use a browser DevTools network tab and check the Protocol column.
Step 5: Add gzip and cache headers tuned for long paths
For static assets, the difference between a cache hit in the user's browser and a full round trip to Vietnam is the difference between 5 ms and 250 ms. Set cache headers aggressively for fingerprinted assets, and use gzip or Brotli as a fallback where Brotli is not supported.
gzip on;
gzip_comp_level 5;
gzip_min_length 1024;
gzip_types text/plain text/css application/javascript application/json image/svg+xml application/xml+rss;
gzip_vary on;
# In a server or location block for static files
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff2)$ {
expires 30d;
add_header Cache-Control "public, immutable";
try_files $uri =404;
}
Verify the cache headers from a remote location:
curl -sI https://your-domain.com/assets/app.js | grep -i cache-control
# expected: public, immutable
Step 6: Route upstreams by user geography
If your AlmaLinux VPS in Vietnam is a reverse proxy for an application hosted elsewhere, the distance between Nginx and your upstream matters as much as the distance to your user. A user in Vietnam hitting an upstream in the US adds a full Vietnam-US round trip on every request, plus the US-Vietnam return leg.
Use the GeoIP2 module to route users from different regions to the closest upstream. Install the module and the GeoLite2 country database:
dnf install nginx-mod-stream
# Download the GeoLite2-Country.mmdb from MaxMind (free account required)
# In the http block
geoip2 /etc/nginx/geo/GeoLite2-Country.mmdb {
$geoip2_data_country_code country iso_code;
}
# In the stream block or using a map
map $geoip2_data_country_code $upstream_pool {
default us_upstream;
VN vn_upstream;
SG MY ID TH sg_upstream;
JP KR HK TW hk_upstream;
DE FR NL GB eu_upstream;
}
This routing logic only helps when your application actually runs in multiple regions. If the origin is a single server, geography routing adds nothing. Then your job is to make that single connection as efficient as possible with the TCP and keepalive settings above, or consider whether the dedicated server route gives you a better network position.
Troubleshooting
Nginx does not start after config changes. Run nginx -t to find the syntax error, often a missing semicolon or a module that is not loaded. Check loaded modules with nginx -V 2>&1 | tr ' ' '\n' | grep http_ssl when a directive is not recognized.
BBR is not active after sysctl --system. Confirm the file is in /etc/sysctl.d/ and ends with .conf. Run sysctl net.ipv4.tcp_congestion_control again; if it still shows cubic, check whether something else overrides it with sysctl --system 2>&1 | grep congestion.
HTTP/3 requests on port 443/UDP are dropped. This is usually a firewall issue. On AlmaLinux 9 the default is firewalld, not ufw. Allow the UDP port explicitly:
firewall-cmd --permanent --add-port=443/udp
firewall-cmd --reload
FAQ
Does TCP BBR actually help a server in Vietnam serving global users?
Yes, on long, lossy paths BBR generally outperforms cubic because it does not treat random packet loss as congestion. You will see the biggest difference on paths to Europe and the US. On short domestic paths the difference is small because the bottleneck is not loss but raw propagation delay.
What is the single most impactful Nginx setting for global latency?
Keeping connections alive long enough for reuse. keepalive_timeout 30s and keepalive_requests 1000 let a distant user load dozens of assets over one TLS connection instead of paying a new handshake per asset.
Is HTTP/3 worth enabling on an AlmaLinux VPS in Vietnam?
For global users, yes. QUIC avoids head-of-line blocking on lossy international paths, which matters when a dropped packet would otherwise stall the entire HTTP/2 connection for one full round trip.
Should I use a CDN instead of tuning Nginx?
A CDN and good origin tuning solve different problems. A CDN caches static content closer to the user, which is effective. But dynamic API responses still cross the full path, and a well-tuned origin makes that path faster for both CDN fetches and direct hits.
How do I measure the latency improvement?
Use curl -w from a machine in the target region, or an external monitoring service. Compare time to first byte before and after the changes. Focus on time_starttransfer, because that reflects the actual user experience.
Related articles
- Advanced Nginx performance tuning on an AlmaLinux VPS
- Enable HTTP/3 and Brotli compression on Nginx
- Nginx tuning for high traffic: worker, gzip, buffer, cache
- Vietnam VPS vs offshore VPS for local traffic
越南机房 Nginx 全球低延迟调优
从越南机房服务器服务全球用户时,物理延迟无法消除,但可以去掉协议层的额外开销。启用 TCP BBR 和增大套接字缓冲区能显著改善长途链路的吞吐。Nginx 层面重点是保持连接复用、开启 TLS 会话缓存,以及为远距离用户启用 HTTP/3 和 Brotli 压缩。按本文步骤修改后,务必用 curl 或浏览器检查验证每个改动是否生效。


