HTTP/2 in Nginx has a version split that matters before editing a live HTTPS server block. Nginx 1.25.1 introduced the standalone http2 on; directive and deprecated the older listen ... http2 parameter, while older 1.9.5 through 1.25.0 builds still use the legacy listener syntax.
The safest workflow is to confirm the installed Nginx version plus HTTP/2 and HTTPS module support, update the HTTPS virtual host with the syntax that the build supports, test the configuration with nginx -t, reload Nginx, then verify that clients negotiate HTTP/2 instead of only reaching a working HTTPS page.
Nginx must already be installed, and the site needs a valid TLS certificate for normal browser HTTP/2. Use the distro-specific guides to Install Nginx on Ubuntu, Install Nginx on Debian, or Install Nginx on Fedora if the web server is not set up yet.
Check Which Nginx HTTP/2 Syntax Your Server Supports
Check the installed Nginx version first. The command prints to standard error on many builds, so the visible output may appear even when you redirect normal output elsewhere:
nginx -v
Example output:
nginx version: nginx/1.31.1
Then confirm that the binary was built with the modules needed for browser HTTP/2 over HTTPS. The official Nginx HTTP/2 module documentation states that HTTP/2 is enabled at build time with --with-http_v2_module, while the Nginx SSL module documentation uses --with-http_ssl_module for HTTPS support.
nginx -V 2>&1 | grep -Eo -- '--with-http_(ssl|v2)_module'
A build that can serve normal browser HTTP/2 over TLS returns both module flags:
--with-http_ssl_module --with-http_v2_module
Use the version and module result to choose the correct configuration form:
| Nginx build | HTTP/2 syntax to use | Practical note |
|---|---|---|
Nginx 1.25.1 and newer with ngx_http_v2_module | listen 443 ssl; plus http2 on; | Current documented form. Avoids the deprecation warning from listen ... http2. |
Nginx 1.9.5 through 1.25.0 with ngx_http_v2_module | listen 443 ssl http2; | Legacy form for older packaged builds that do not know the standalone http2 directive. |
Custom build missing --with-http_v2_module or --with-http_ssl_module | No normal HTTPS HTTP/2 workflow works until the missing module is present | Install a package or rebuild Nginx with the required module before editing site configuration. |
The official Nginx listen directive documentation still documents the old http2 listener parameter, but it marks that parameter as deprecated and points to the standalone http2 directive instead. Nginx announced that change in the Nginx 1.25.1 release notes.
The same HTTP/2 module documentation notes that HTTP/2 over TLS requires ALPN support from OpenSSL 1.0.2 or newer. Modern distro and nginx.org packages normally satisfy that requirement, but very old or custom-linked builds can parse the directive and still fail client negotiation.
Enable HTTP/2 in Current Nginx Releases
On Nginx 1.25.1 and newer, keep TLS on the listen directive and enable HTTP/2 with http2 on; inside the HTTPS server block. This keeps the socket configuration and protocol selection separate.
Find the active HTTPS server block before editing. nginx -T prints the loaded configuration, including included files, while the filter keeps each matched directive grouped under the file that owns it:
sudo nginx -T 2>/dev/null | awk '/^# configuration file / { file=$0; next } /^[[:space:]]*(listen[[:space:]].*443|server_name|http2)/ { if (file != last) { print file; last=file } print }'
Relevant lines might look like this:
# configuration file /etc/nginx/conf.d/example.com.conf:
listen 443 ssl;
listen [::]:443 ssl;
server_name example.com www.example.com;
Back up the file that owns the HTTPS server block. Replace the path with the real file shown by your Nginx configuration, such as a file under /etc/nginx/conf.d/, /etc/nginx/sites-available/, or another include directory used by your package or source build. If no listen 443 line appears, configure HTTPS before enabling browser HTTP/2.
sudo cp -a /etc/nginx/conf.d/example.com.conf /etc/nginx/conf.d/example.com.conf.bak
Edit the HTTPS server block so the TLS listeners stay on listen 443 ssl; and HTTP/2 is enabled with a separate directive. Replace the certificate, document-root, and domain values with the paths and names used by your site:
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name example.com www.example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
root /var/www/example.com/public_html;
index index.html;
location / {
try_files $uri $uri/ =404;
}
}
Place http2 on; in the server block when you want to control HTTP/2 per virtual host. The directive is also allowed in the http context, but that enables HTTP/2 for inherited server blocks unless a more specific configuration disables it.
Do not add
http2 on;to the port 80 redirect-only server block. Normal browser HTTP/2 uses TLS with ALPN, and the plaintext redirect block only needs to send clients to HTTPS.
Test the configuration before reloading the service:
sudo nginx -t
A clean syntax test reports success:
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok nginx: configuration file /etc/nginx/nginx.conf test is successful
Reload Nginx only after the syntax test passes:
sudo systemctl reload nginx
If the HTTPS server block is part of a reverse proxy, keep the browser-to-Nginx protocol separate from the upstream protocol. HTTP/2 can be active at the public TLS edge while Nginx still proxies to an application over HTTP/1.1, FastCGI, or another backend protocol. For backend header and upstream routing details, use the Nginx reverse proxy configuration.
Enable HTTP/2 on Older Nginx Builds
Use the older listener syntax only when your Nginx build is 1.9.5 through 1.25.0 and rejects http2 on;. In that case, add http2 to each HTTPS listener that should accept HTTP/2:
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name example.com www.example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
root /var/www/example.com/public_html;
}
Do not mix both forms in the same server block as a compatibility shortcut. Use http2 on; on current Nginx releases, and keep listen 443 ssl http2; only for older packages that need it.
Current Nginx releases still parse the old form, but sudo nginx -t warns during the syntax test:
nginx: [warn] the "listen ... http2" directive is deprecated, use the "http2" directive instead
If that warning appears on Nginx 1.25.1 or newer, remove http2 from the listen lines and add http2 on; inside the same HTTPS server block. Then rerun sudo nginx -t before reloading.
Nginx releases older than 1.9.5 do not provide the ngx_http_v2_module module. Upgrade Nginx instead of trying either HTTP/2 directive on those builds.
Verify That Nginx Negotiates HTTP/2
A working HTTPS page does not prove HTTP/2. Use curl with HTTP/2 requested, print the response headers, and check the first status line. Replace example.com with the hostname in the HTTPS server block:
curl --noproxy '*' --http2 -sS -D - -o /dev/null https://example.com/
The first line should start with HTTP/2. The status code can be 200, 301, 302, 404, or another expected application status; the protocol token is the part that proves negotiation.
HTTP/2 200
If curl says the installed libcurl does not support HTTP/2, use the OpenSSL ALPN check in this section or install a curl build linked with HTTP/2 support. The Nginx configuration can be correct even when the local client cannot request HTTP/2.
For a direct origin test before public DNS changes, map the hostname to the origin IP while preserving the HTTPS hostname and SNI value:
curl --noproxy '*' --http2 --resolve example.com:443:203.0.113.10 -sS -D - -o /dev/null https://example.com/
Replace 203.0.113.10 with the origin server address. This is more accurate than a bare IP request because it preserves the Host header, TLS SNI, and certificate name that Nginx uses to select the correct server block.
If you want to inspect ALPN directly, use OpenSSL to ask the server for h2 during the TLS handshake:
openssl s_client -connect example.com:443 -servername example.com -alpn h2 </dev/null 2>/dev/null | grep -i 'ALPN protocol'
A successful ALPN negotiation reports h2:
ALPN protocol: h2
If the site is behind a CDN, load balancer, hosting panel, Docker proxy, or Nginx Proxy Manager, verify the layer that terminates public TLS. Origin Nginx can have HTTP/2 enabled while the public edge still answers clients over HTTP/1.1, or the edge can provide HTTP/2 while the origin remains HTTP/1.1.
Keep HTTP/2 Tuning Directives Current
Enabling HTTP/2 usually needs only http2 on;. Many old tuning snippets still include directives that current Nginx documentation marks as obsolete, especially copies from older server-push or header-size examples.
| Old or current directive | Current status | Use instead or keep when needed |
|---|---|---|
http2_max_concurrent_streams | Current | Keep only if you need to change the default maximum concurrent streams per connection. |
http2_body_preread_size | Current | Keep only when request-body buffering behavior has a specific reason to change. |
http2_chunk_size | Current | Rarely needed; a low value adds overhead while a high value can affect prioritization. |
http2_recv_buffer_size | Current | Keep only when the default per-worker HTTP/2 input buffer is too small for a measured workload. |
http2_idle_timeout | Obsolete since Nginx 1.19.7 | Use keepalive_timeout. |
http2_max_field_size and http2_max_header_size | Obsolete since Nginx 1.19.7 | Use large_client_header_buffers. |
http2_max_requests | Obsolete since Nginx 1.19.7 | Use keepalive_requests. |
http2_recv_timeout | Obsolete since Nginx 1.19.7 | Use client_header_timeout. |
http2_push, http2_push_preload, and http2_max_concurrent_pushes | Obsolete since Nginx 1.25.1 | Remove server-push configuration from current Nginx builds. |
For HTTPS hardening, keep HTTP/2 separate from browser policy headers. HSTS, Content Security Policy, X-Frame-Options, and related headers are configured independently from protocol negotiation. Use the Nginx security headers guide after HTTP/2 is working.
Troubleshoot Nginx HTTP/2 Problems
Fix the Deprecated listen http2 Warning
The warning means Nginx found the old listener parameter on a build that supports the newer directive:
nginx: [warn] the "listen ... http2" directive is deprecated, use the "http2" directive instead
Find every old-style HTTP/2 listener in the active configuration before editing:
sudo nginx -T 2>/dev/null | awk '/^# configuration file / { file=$0; next } /^[[:space:]]*listen[[:space:]].*http2/ { if (file != last) { print file; last=file } print }'
Change each matching HTTPS server block from the deprecated form:
listen 443 ssl http2;
listen [::]:443 ssl http2;
Use the current form instead:
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
Retest first and confirm the deprecation warning no longer appears:
sudo nginx -t
Reload Nginx only after the syntax test succeeds without the deprecated listener warning:
sudo systemctl reload nginx
Fix unknown directive http2
An unknown directive "http2" error usually means one of two things: the Nginx build is older than 1.25.1, or the HTTP/2 module is missing from a custom build.
Check both conditions before changing the configuration again:
nginx -v
nginx -V 2>&1 | grep -Eo -- '--with-http_(ssl|v2)_module'
If the version is older than 1.25.1 and both module flags exist, use the legacy listen 443 ssl http2; syntax. If either module flag is absent, install a packaged Nginx build that includes HTTP/2 and HTTPS support or rebuild Nginx with the missing --with-http_v2_module or --with-http_ssl_module option.
Fix curl still showing HTTP/1.1
If curl --http2 still reports HTTP/1.1, first confirm that the request reaches the expected HTTPS server block rather than a default virtual host, CDN edge, or proxy manager template.
sudo nginx -T 2>/dev/null | awk '/^# configuration file / { file=$0; next } /^[[:space:]]*(listen[[:space:]].*443|server_name|http2)/ { if (file != last) { print file; last=file } print }'
If the active server block is correct, check the final TLS layer with the same hostname clients use:
openssl s_client -connect example.com:443 -servername example.com -alpn h2 </dev/null 2>/dev/null | grep -i 'ALPN protocol'
No ALPN protocol: h2 line means the TLS endpoint did not negotiate HTTP/2. Check whether the certificate terminates at a CDN, load balancer, hosting panel, or container gateway before changing the origin Nginx file again.
Fix HTTP/2 After Changing Ports or Server Blocks
Custom HTTPS ports need the same syntax split as port 443. On current Nginx, keep http2 on; separate from listen 8443 ssl;; on older Nginx builds, use listen 8443 ssl http2;. The Nginx port configuration guide covers listener discovery, firewall handoff, and custom-port verification in more depth.
Use Logs When Syntax Passes but Negotiation Fails
When nginx -t succeeds but clients still negotiate the wrong protocol, inspect the active access and error logs for the requested hostname, status, and upstream layer. The Nginx access and error logs guide explains how to separate config syntax, route selection, proxy failures, and application errors.
Roll Back HTTP/2 Changes
If a production site fails after the change, restore the backup of the edited server-block file:
sudo cp -a /etc/nginx/conf.d/example.com.conf.bak /etc/nginx/conf.d/example.com.conf
Retest the restored configuration before applying it:
sudo nginx -t
Reload only after the restored configuration passes:
sudo systemctl reload nginx
When a panel or proxy manager generates Nginx configuration, roll back in that tool instead of editing generated files directly. Manual edits can disappear the next time the control plane rebuilds its templates.
Conclusion
Nginx is serving HTTPS with HTTP/2 enabled through the syntax that matches its installed version, with syntax tests and protocol probes confirming the change. From here, tighten the surrounding server block with security headers, review Nginx server block structure, or tune response delivery with gzip compression.


Formatting tips for your comment
You can use basic HTML to format your comment. Useful tags currently allowed in published comments:
<code>command</code>command<strong>bold</strong><em>italic</em><blockquote>quote</blockquote>