NGINX Hardening: Hide Version & Top 10 Security Fixes
Stop NGINX leaking your exact version to bots. Add server_tokens off, apply ten practical hardening fixes, and verify headers from an Australian VPS or cloud host—DIY steps plus when to book Fixwebnode.
If you run NGINX on a VPS or cloud instance in Australia, default configs often advertise your exact software version in every HTTP response—giving scanners a free blueprint of what to exploit. This guide shows you how to turn that off, apply a complete top-10 web server security checklist, and verify the result with real commands. Fixwebnode provides direct remote Linux and NGINX server support for Australian site owners and small businesses who need the work done properly without marketplace middlemen—start at Ubuntu Linux server support.
Scope assumes Ubuntu/Debian-style paths (/etc/nginx/), root or sudo access, and a live site you can test with curl. Always back up configs before editing.
Why default NGINX configs hand hackers a blueprint
Out of the box, NGINX commonly returns a Server: nginx/1.18.0 (or similar) header and may embed the same string on error pages. Automated bots scrape that header, match it to known CVEs, and prioritise your host. Hiding the version does not replace patching—but it removes an easy reconnaissance signal and pairs well with the broader hardening steps below.
Australian businesses on shared VPS panels often inherit stock configs from images or control panels. A five-minute config change plus a controlled reload is usually enough to stop the leak; deeper issues (wrong include files, reverse-proxy headers, PHP fingerprints) need a short checklist.
How do I stop NGINX showing its version on an Australian server?
Add server_tokens off; inside the http block (or each server block), test the config, reload NGINX, then confirm the Server header no longer includes a version string. If the version still appears, you are almost always editing the wrong file, missing a reload, or seeing a backend/proxy header instead of NGINX itself.
| Symptom | Quick fix | When to call Fixwebnode |
|---|---|---|
Server: nginx/1.x.x still present | Set server_tokens off;, nginx -t, reload | Multiple conf includes or panel-managed NGINX |
| Version on custom error pages only | Tokens off + custom error_page files | Complex vhost / CDN stack |
| App still leaks stack (e.g. PHP) | Strip X-Powered-By and proxy headers | Full stack audit across app + edge |
Common issues that keep the version (or stack) visible
1. server_tokens set, but the live vhost never loads it
Symptom: You edited nginx.conf, yet curl -I still shows nginx/1.xx. Often the active site only includes snippets under sites-enabled/ that redefine behaviour, or a second NGINX binary/container is answering the public IP.
2. Error pages and directory indexes still print the build string
Symptom: Normal responses look clean, but 404/50x pages or autoindex listings still say “nginx/1.xx”. Tokens alone may not restyle default error bodies if custom pages were never deployed.
3. Backend or proxy headers still fingerprint the stack
Symptom: Server is generic, but X-Powered-By: PHP/8.x, X-Generator, or upstream Via/X-Backend-Server headers remain. Attackers do not need the NGINX patch level if PHP or the app framework is advertised.
Fix issue 1 — Make server_tokens actually apply
Confirm which process owns port 80/443, edit the correct config, validate, reload, and re-check headers.
Step 1 — See what answers publicly
curl -sI https://YOUR_DOMAIN | grep -i '^server:'
ss -tlnp | grep -E ':80|:443'
ps aux | grep '[n]ginx'Note the binary path and whether Docker or a host NGINX is bound.
Step 2 — Back up and set tokens off in the http block
sudo cp -a /etc/nginx/nginx.conf /etc/nginx/nginx.conf.bak.$(date +%F)
sudo nano /etc/nginx/nginx.confInside the main http { ... } block add:
server_tokens off;Optionally repeat inside each server { } for clarity. Prefer the http block so every vhost inherits it.
Step 3 — Test and reload without dropping good configs
sudo nginx -t
sudo systemctl reload nginxExpected: syntax is ok and test is successful. If nginx -t fails, do not reload—restore the backup and fix the typo.
Step 4 — Verify
curl -sI https://YOUR_DOMAIN | grep -i '^server:'
curl -sI http://127.0.0.1/ | grep -i '^server:'You want Server: nginx with no version, or a custom token if you set one via third-party modules. Empty or generic is the goal on stock NGINX.
When to call Fixwebnode: panel-managed stacks (Plesk, cPanel, custom Ansible) that overwrite conf on deploy, or dual NGINX + ingress setups where the edge still leaks version.
Fix issue 2 — Clean error pages and indexes
Step 1 — Disable autoindex where it is not required
# inside the relevant server or location block
autoindex off;Step 2 — Point errors at static pages you control
error_page 404 /custom_404.html;
location = /custom_404.html {
root /var/www/html;
internal;
}Create simple HTML files that do not mention software versions. Retest with a deliberate bad URL:
curl -sI https://YOUR_DOMAIN/this-path-should-404 | grep -i server
curl -s https://YOUR_DOMAIN/this-path-should-404 | headWhen to call Fixwebnode: multi-site farms with shared error handlers or CDN origin shields that inject their own bodies.
Fix issue 3 — Stop backend fingerprint headers
Step 1 — Hide PHP version (php-fpm / php.ini)
# php.ini or pool override
expose_php = Offsudo systemctl reload php8.3-fpm
# adjust version: php8.1-fpm, php8.2-fpm, etc.Step 2 — Strip hop-by-hop / noisy headers at NGINX
proxy_hide_header X-Powered-By;
fastcgi_hide_header X-Powered-By;
proxy_hide_header X-Generator;
more_clear_headers 'Server'; # only if headers-more module is installedStep 3 — Re-check full response headers
curl -sI https://YOUR_DOMAINRemove anything that names frameworks, CMS builds, or internal hostnames.
When to call Fixwebnode: when headers reappear after deploys, or microservices add new identifying headers each release.
Complete top 10 NGINX web server security fixes
Apply these in order on a maintenance window. Each item is production-oriented and verifiable.
1. server_tokens off (version disclosure)
Covered above. Non-negotiable baseline.
2. Enforce HTTPS and modern TLS only
server {
listen 443 ssl http2;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:10m;
}sudo nginx -t && sudo systemctl reload nginx
curl -sI https://YOUR_DOMAIN | grep -i strict-transportRedirect port 80 to HTTPS with a simple return 301 https://$host$request_uri; server block.
3. Send core security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
# HSTS only after you confirm HTTPS works everywhere:
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;4. Disable dangerous HTTP methods
if ($request_method !~ ^(GET|HEAD|POST)$) {
return 405;
}Prefer limit_except inside sensitive location blocks when you need finer control.
5. Limit request size and slow-loris style abuse
client_max_body_size 10m;
client_body_timeout 12s;
client_header_timeout 12s;
send_timeout 10s;
large_client_header_buffers 4 16k;6. Rate-limit login and expensive endpoints
# in http block
limit_req_zone $binary_remote_addr zone=login:10m rate=5r/m;
# in server/location for /wp-login.php, /admin, /api/auth, etc.
limit_req zone=login burst=10 nodelay;7. Deny hidden files and sensitive paths
location ~ /\. {
deny all;
}
location ~* (?:\.(?:bak|old|sql|env|git|svn|swp)$) {
deny all;
}
location ^~ /.well-known/acme-challenge/ {
allow all;
}8. Lock down default server / unknown hosts
server {
listen 80 default_server;
listen 443 ssl default_server;
server_name _;
ssl_reject_handshake on; # if supported by your NGINX build
return 444;
}Stops random Host-header probes hitting your first real vhost.
9. Run as a non-privileged user and tighten file permissions
grep -E '^(user|pid)' /etc/nginx/nginx.conf
sudo chown -R root:root /etc/nginx
sudo find /etc/nginx -type f -exec chmod 640 {} \;
sudo chmod 750 /etc/nginx
# site content owned by deploy user, readable by www-data10. Patch NGINX, reload cleanly, and watch logs
sudo apt-get update
sudo apt-get install --only-upgrade nginx
sudo nginx -t && sudo systemctl reload nginx
sudo tail -n 100 /var/log/nginx/error.log
sudo tail -n 100 /var/log/nginx/access.logPair with host firewall (ufw allow 22,80,443/tcp only as needed) and fail2ban on SSH and auth URLs. Keep backups of /etc/nginx before every change.
Quick remote verification checklist
- From your laptop:
curl -sI https://YOUR_DOMAIN— no version inServer, security headers present. - On the server:
sudo nginx -T 2>/dev/null | grep -i server_tokens— confirms the directive is in the effective config dump. - TLS: use a scanner you trust or
openssl s_client -connect YOUR_DOMAIN:443 -tls1_1and expect failure once TLS 1.0/1.1 are disabled. - After reload failures:
journalctl -u nginx -e --no-pagerfor unit errors.
When DIY is enough vs when to book Fixwebnode
DIY is enough when you have SSH, a single Ubuntu/Debian NGINX host, and can complete nginx -t plus header checks without breaking TLS. The steps above are safe if you back up configs and reload only after a clean test.
Book Fixwebnode when conf is regenerated by a panel, when multiple reverse proxies or Kubernetes ingress layers disagree on headers, when reloads take the site offline, or when you need a full hardening pass (TLS, rate limits, PHP pool, firewall) under change control. Support is direct specialist remote work for Australian businesses—not a freelance marketplace. See where coverage is described on the service areas page, then continue via the Linux server landing link below.
Book remote NGINX hardening with Fixwebnode
If your headers still expose a blueprint, or you want the full top-10 checklist applied and verified on your host, talk to Fixwebnode for direct remote server support. Start the conversation through Fixwebnode Ubuntu Linux server support and outline your NGINX version, distro, and whether the site sits behind a CDN or panel. We will focus on stopping version leaks, locking TLS and headers, and leaving you with configs you can re-verify with curl after every deploy.