Skip to content

Nginx TLS on Windows: certificates, HTTP/2 and sane defaults

Verified 1 min read

Configure PEM certificates, TLS 1.2/1.3, HTTP/2, SNI and HSTS on Windows Nginx, and test the handshake instead of trusting a browser padlock.

Tested onStandard / Datacenter · Build 26100.33650 · x64
E1
Build
26100.33650
Edition
Standard / Datacenter
Architecture
x64
Support
Documentation (E1)
Evidence
E1Official documentation
Verified
2026-10-29
Page type
Task

Nginx on Windows reads PEM files, not the Windows certificate store:

server {
listen 443 ssl;
http2 on;
server_name lab.local;
ssl_certificate C:/nginx/certs/lab.crt;
ssl_certificate_key C:/nginx/certs/lab.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
}

Export the certificate + private key as PEM (full chain first), restrict the key’s ACL, and test with nginx -t.

server {
listen 80;
server_name lab.local;
return 301 https://$host$request_uri;
}

Do not add HSTS until HTTPS works on every name; HSTS is cached by browsers.

add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;

CSP is application-specific; do not paste a random CSP into every site. A wrong CSP breaks the app and gets removed, which teaches nothing.

One server block per certificate/name group. Nginx selects the block by SNI. A client that does not send SNI gets the default_server certificate.

server {
listen 443 ssl default_server;
server_name _;
ssl_certificate C:/nginx/certs/default.crt;
ssl_certificate_key C:/nginx/certs/default.key;
return 444;
}

return 444 closes the connection for unmatched names; do not serve a random site’s certificate.

Terminal window
cd C:\nginx
.\nginx.exe -t
.\nginx.exe -s reload
curl.exe -I --resolve lab.local:443:127.0.0.1 https://lab.local/ -k
openssl s_client -connect 127.0.0.1:443 -servername lab.local -tls1_3

Check:

  • certificate subject/SAN matches the hostname;
  • TLS 1.0/1.1 are not offered;
  • HTTP/2 is negotiated (curl --http2 shows HTTP/2);
  • redirect goes to the public hostname, not an internal one.
  • Nginx does not read Cert:\LocalMachine\My; export PFX, then convert to PEM/key.
  • Keep the private key off the web root; ACL it to the Nginx service account + Administrators.
  • Renewal must reload Nginx (nginx -s reload), not just replace the file.
  • Self-signed is lab-only; clients must trust it explicitly.

E1. The lab served Nginx over HTTP on 8081; TLS, SNI and HTTP/2 were not reproduced on Windows. Validate on Server 2025 with a real certificate before claiming E2.

Sources