# Replacing Apache with Nginx for HTTP2 support

**URL:** <https://community.letsencrypt.org/t/replacing-apache-with-nginx-for-http2-support/12674>\
**Category:** Uncategorized\
**Created:** [March 17, 2016, 5:05am UTC](https://community.letsencrypt.org/t/replacing-apache-with-nginx-for-http2-support/12674 "2016-03-17T05:05:44Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![dominikwilkowski](https://sea3.discourse-cdn.com/letsencrypt/user_avatar/community.letsencrypt.org/dominikwilkowski/32/6003_2.png) [@dominikwilkowski](https://community.letsencrypt.org/u/dominikwilkowski)\
**Post date:** [March 17, 2016, 5:05am UTC](https://community.letsencrypt.org/t/replacing-apache-with-nginx-for-http2-support/12674/1 "2016-03-17T05:05:44Z")

</div>

Hey guys,

I’ve used letsencrypt to install an SSL cert for the latest nginx on ubuntu.  
The setup is fine and works great with the exception of:

 ![](https://global.discourse-cdn.com/letsencrypt/original/2X/7/7f55e485c1fa0e0350362126e8201e3c94649b95.png)

I don’t know enough about SSL to know what’s going on but I have a suspicion:  
I installed the SSL cert for Apache a while back and just now moved to Nginx for it’s http/2 support. As the nginx plugin is not stable yet I had to install the cert myself and this is what I did:

In my nginx config (`/etc/nginx/conf/default.conf`) I added:

```
server {
	listen 80;
	server_name [domain];
	return 301 https://$host$request_uri;
}

server {
	listen 443 http2;
	listen [::]:443 http2;
	server_name [domain];

	ssl on;
	ssl_certificate /etc/letsencrypt/live/[domain]/cert.pem;
	ssl_certificate_key /etc/letsencrypt/live/[domain]/privkey.pem;
}

```

Is it possible that this breaks the chain somehow? What is the proper way here?

Thanks guys

---

<div class="post-metadata">

**Author:** ![motoko](https://sea3.discourse-cdn.com/letsencrypt/user_avatar/community.letsencrypt.org/motoko/32/26328_2.png) [@motoko](https://community.letsencrypt.org/u/motoko)\
**Post date:** [March 17, 2016, 6:37am UTC](https://community.letsencrypt.org/t/replacing-apache-with-nginx-for-http2-support/12674/2 "2016-03-17T06:37:22Z")

</div>

Use “fullchain.pem” for ssl\_certificate. It has the certificate plus the chain back to the CA.

If you want a better score, also look into customizing the nginx SSL configuration so you’re using custom-generated DH parameters.

---

<div class="post-metadata">

**Author:** ![kelunik](https://sea3.discourse-cdn.com/letsencrypt/user_avatar/community.letsencrypt.org/kelunik/32/359_2.png) [@kelunik](https://community.letsencrypt.org/u/kelunik)\
**Post date:** [March 17, 2016, 7:14am UTC](https://community.letsencrypt.org/t/replacing-apache-with-nginx-for-http2-support/12674/3 "2016-03-17T07:14:42Z")

</div>

Have a look at [https://wiki.mozilla.org/Security/Server\_Side\_TLS#Intermediate\_compatibility\_.28default.29](https://wiki.mozilla.org/Security/Server_Side_TLS#Intermediate_compatibility_.28default.29) for configuring the cipher suites. There’s also a generator at [https://mozilla.github.io/server-side-tls/ssl-config-generator/](https://mozilla.github.io/server-side-tls/ssl-config-generator/)

---

<div class="post-metadata">

**Author:** ![leader](https://sea3.discourse-cdn.com/letsencrypt/user_avatar/community.letsencrypt.org/leader/32/7569_2.png) [@leader](https://community.letsencrypt.org/u/leader)\
**Post date:** [March 17, 2016, 8:33am UTC](https://community.letsencrypt.org/t/replacing-apache-with-nginx-for-http2-support/12674/4 "2016-03-17T08:33:21Z")

</div>

ssl\_ciphers ‘HIGH:!aNULL:!MD5:!kEDH’;

---

<div class="post-metadata">

**Author:** ![dominikwilkowski](https://sea3.discourse-cdn.com/letsencrypt/user_avatar/community.letsencrypt.org/dominikwilkowski/32/6003_2.png) [@dominikwilkowski](https://community.letsencrypt.org/u/dominikwilkowski)\
**Post date:** [March 17, 2016, 9:56am UTC](https://community.letsencrypt.org/t/replacing-apache-with-nginx-for-http2-support/12674/5 "2016-03-17T09:56:38Z")

</div>

Thanks guys! That helped a lot!

I ended up with these configs:

```
# Settings
server_tokens off;

server {
	listen 80;
	server_name [domain];
	return 301 https://$host$request_uri;
}

server {
	listen 443 http2;
	listen [::]:443 http2;
	server_name [domain];

	ssl on;
	ssl_certificate /etc/letsencrypt/live/[domain]/fullchain.pem;
	ssl_certificate_key /etc/letsencrypt/live/[domain]/privkey.pem;

	ssl_session_timeout 1d;
	ssl_session_cache shared:SSL:50m;
	ssl_session_tickets off;

	ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
	ssl_prefer_server_ciphers on;
	ssl_dhparam /etc/nginx/ssl/dhparam.pem;
	ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA';

	# OCSP Stapling ---
	# fetch OCSP records from URL in ssl_certificate and cache them
	ssl_stapling on;
	ssl_stapling_verify on;

	# HSTS (ngx_http_headers_module is required) (15768000 seconds = 6 months)
	add_header Strict-Transport-Security max-age=15768000;
}

```

and the result is:

 ![](https://global.discourse-cdn.com/letsencrypt/original/2X/c/c2cbd780f443326a79a2458d3ce929cf2b492d99.png)

---

<div class="post-metadata">

**Author:** ![kelunik](https://sea3.discourse-cdn.com/letsencrypt/user_avatar/community.letsencrypt.org/kelunik/32/359_2.png) [@kelunik](https://community.letsencrypt.org/u/kelunik)\
**Post date:** [March 17, 2016, 10:45am UTC](https://community.letsencrypt.org/t/replacing-apache-with-nginx-for-http2-support/12674/6 "2016-03-17T10:45:01Z")

</div>

With HSTS you should usually get an A+. You can add `add_header Strict-Transport-Security max-age=15768000 always;` if your Nginx version allows for `always`.

---

<div class="post-metadata">

**Author:** ![Osiris](https://avatars.discourse-cdn.com/v4/letter/o/839c29/32.png) [@Osiris](https://community.letsencrypt.org/u/Osiris)\
**Post date:** [March 17, 2016, 12:29pm UTC](https://community.letsencrypt.org/t/replacing-apache-with-nginx-for-http2-support/12674/7 "2016-03-17T12:29:57Z")

</div>

I was under the impression you should always list `ssl` to the `listen 443` directive in nginx to enable TLS? Or does HTTP2 imply TLS?

Hmm, the sample configuration in the [documentation of nginx about HTTP2](http://nginx.org/en/docs/http/ngx_http_v2_module.html) also mentions `listen 443 ssl http2;`…

By the way, [Apache has support for HTTP2 too](https://httpd.apache.org/docs/2.4/mod/mod_http2.html) 😉

---

<div class="post-metadata">

**Author:** ![pfg](https://sea3.discourse-cdn.com/letsencrypt/user_avatar/community.letsencrypt.org/pfg/32/1924_2.png) [@pfg](https://community.letsencrypt.org/u/pfg)\
**Post date:** [March 17, 2016, 1:09pm UTC](https://community.letsencrypt.org/t/replacing-apache-with-nginx-for-http2-support/12674/8 "2016-03-17T13:09:22Z")

</div>

HTTP/2 implies TLS in practice, because all browser vendors decided to implement it only with TLS.

I wonder if skipping `ssl` means that only clients supporting HTTP/2 can use TLS, though. Would be surprising if SSL Labs doesn’t catch that. Might be a good idea for @dominikwilkowski to try the site with a client without HTTP/2 support, just in case.

---

<div class="post-metadata">

**Author:** ![CloudInsidr](https://sea3.discourse-cdn.com/letsencrypt/user_avatar/community.letsencrypt.org/cloudinsidr/32/33154_2.png) [@CloudInsidr](https://community.letsencrypt.org/u/CloudInsidr)\
**Post date:** [March 17, 2016, 8:34pm UTC](https://community.letsencrypt.org/t/replacing-apache-with-nginx-for-http2-support/12674/9 "2016-03-17T20:34:42Z")

</div>

Hi dominikwilkowski,

**Excellent question!** 🙂  
I had lots of questions like yours, i.e. HTTP/2 with NGINX frequently, so I decided to write a post about it:

The Diffie-Hellman with 1024 and 2048 is considered weak, because using massive precomputation (with clouds like AWS, Azure and Google Cloud) that’s no longer state of the art.

Besides you should also stay away from SSL v2, SSL v3 and TLS 1.0. Secure as of today are only TLS v1.1 and v.1.2!

So in the NGINX conf it looks like this:

ssl\_protocols TLSv1.1 TLSv1.2;

How to Activate HTTP/2 with TLS Encryption in NGINX for Secure Connections without a Performance Penalty

> **[How to Activate HTTP/2 with TLS 1.3 Encryption in NGINX for Secure...](https://www.cloudinsidr.com/content/how-to-activate-http2-with-ssltls-encryption-in-nginx-for-secure-connections/)**
>
> Are you ready for a better security with no performance penalty? Are you ready for a performance bump that can take you places in search engine land? In other words: now that HTTP/2 reached production-grade maturity, nothing should hold you...

 ![](https://global.discourse-cdn.com/letsencrypt/original/2X/4/4ce10bd72cbd72c0cc9b6d108854604500b3f023.png)

I hope it helps! 🙂

Cheers,

---

<div class="post-metadata">

**Author:** ![dominikwilkowski](https://sea3.discourse-cdn.com/letsencrypt/user_avatar/community.letsencrypt.org/dominikwilkowski/32/6003_2.png) [@dominikwilkowski](https://community.letsencrypt.org/u/dominikwilkowski)\
**Post date:** [March 17, 2016, 9:46pm UTC](https://community.letsencrypt.org/t/replacing-apache-with-nginx-for-http2-support/12674/10 "2016-03-17T21:46:33Z")

</div>

@kelunik Done that though no change in classification. Still A but I will follow @CloudInsidr post to get a better rating.

---

<div class="post-metadata">

**Author:** ![dominikwilkowski](https://sea3.discourse-cdn.com/letsencrypt/user_avatar/community.letsencrypt.org/dominikwilkowski/32/6003_2.png) [@dominikwilkowski](https://community.letsencrypt.org/u/dominikwilkowski)\
**Post date:** [March 17, 2016, 9:52pm UTC](https://community.letsencrypt.org/t/replacing-apache-with-nginx-for-http2-support/12674/11 "2016-03-17T21:52:52Z")

</div>

@pfg No issues when surfing the website with IE9:  
 ![](https://global.discourse-cdn.com/letsencrypt/original/2X/0/0765c226fc62e9a2f7f44400d1cb4b41c8627502.png)

So I assume `http2` falls back to `SSL`…

---

<div class="post-metadata">

**Author:** ![dominikwilkowski](https://sea3.discourse-cdn.com/letsencrypt/user_avatar/community.letsencrypt.org/dominikwilkowski/32/6003_2.png) [@dominikwilkowski](https://community.letsencrypt.org/u/dominikwilkowski)\
**Post date:** [March 17, 2016, 10:05pm UTC](https://community.letsencrypt.org/t/replacing-apache-with-nginx-for-http2-support/12674/12 "2016-03-17T22:05:19Z")

</div>

@CloudInsidr Thanks. This is a great resource. I will likely also spread the word and, with your permission, will link to your article.

Question though: What is the `& db` part in `sudo openssl dhparam -out /etc/nginx/ssl/dhparam.pem 4096 & bg`?

---

<div class="post-metadata">

**Author:** ![dominikwilkowski](https://sea3.discourse-cdn.com/letsencrypt/user_avatar/community.letsencrypt.org/dominikwilkowski/32/6003_2.png) [@dominikwilkowski](https://community.letsencrypt.org/u/dominikwilkowski)\
**Post date:** [March 17, 2016, 11:03pm UTC](https://community.letsencrypt.org/t/replacing-apache-with-nginx-for-http2-support/12674/13 "2016-03-17T23:03:26Z")

</div>

Ah it does the job in the background… Got it 🙂

I did your changes to this:

```
# Settings
server_tokens off;

server {
	listen 80;
	server_name [domain];
	return 301 https://$host$request_uri;
}

server {
	listen 443 http2;
	listen [::]:443 http2;
	server_name [domain];
	
	ssl on;
	ssl_certificate /etc/letsencrypt/live/[domain]/fullchain.pem;
	ssl_certificate_key /etc/letsencrypt/live/[domain]/privkey.pem;
	
	ssl_session_timeout 1d;
	ssl_session_cache shared:SSL:50m;
	ssl_session_tickets off;
	
	ssl_protocols TLSv1.1 TLSv1.2;
	ssl_prefer_server_ciphers on;
	ssl_dhparam /etc/nginx/ssl/dhparam.pem;
	ssl_ciphers 'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS';
	
	# OCSP Stapling ---
	# fetch OCSP records from URL in ssl_certificate and cache them
	ssl_stapling on;
	ssl_stapling_verify on;
	
	# HSTS (requires ngx_http_headers_module) 
	# 15768000 seconds = 6 months
	# in seconds, 365 days, including subdomains
	# do NOT use max-age of zero; it will disable the policy!
	add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; always";
}

```

Unfortunately it still doesn’t give me A+ though:

 ![](https://global.discourse-cdn.com/letsencrypt/original/2X/f/f9fe863e64a8f9f9c383a77f467ad638138d98f5.png)

I tried to disable `TLSv1.1` as well and ran the test again. No dice.

---

<div class="post-metadata">

**Author:** ![kelunik](https://sea3.discourse-cdn.com/letsencrypt/user_avatar/community.letsencrypt.org/kelunik/32/359_2.png) [@kelunik](https://community.letsencrypt.org/u/kelunik)\
**Post date:** [March 17, 2016, 11:29pm UTC](https://community.letsencrypt.org/t/replacing-apache-with-nginx-for-http2-support/12674/14 "2016-03-17T23:29:11Z")

</div>

What’s your domain? Maybe we can find out the difference then if we have a full report.

---

<div class="post-metadata">

**Author:** ![dominikwilkowski](https://sea3.discourse-cdn.com/letsencrypt/user_avatar/community.letsencrypt.org/dominikwilkowski/32/6003_2.png) [@dominikwilkowski](https://community.letsencrypt.org/u/dominikwilkowski)\
**Post date:** [March 18, 2016, 12:38am UTC](https://community.letsencrypt.org/t/replacing-apache-with-nginx-for-http2-support/12674/15 "2016-03-18T00:38:15Z")

</div>

@kelunik [https://gel.westpacgroup.com.au](https://gel.westpacgroup.com.au)

---

<div class="post-metadata">

**Author:** ![CloudInsidr](https://sea3.discourse-cdn.com/letsencrypt/user_avatar/community.letsencrypt.org/cloudinsidr/32/33154_2.png) [@CloudInsidr](https://community.letsencrypt.org/u/CloudInsidr)\
**Post date:** [March 18, 2016, 1:03am UTC](https://community.letsencrypt.org/t/replacing-apache-with-nginx-for-http2-support/12674/16 "2016-03-18T01:03:32Z")

</div>

Wow, congratulations, you got on top of it in no time:-)

& bg sends the task to the background (longer keys tend to take some measurable time, so should the remote connection be interrupted, your remote machine will still get to complete the Diffie-Hellman group even though your shell is no longer active).

You may also want to deactivate TLS 1.0, as it is vulnerable to downgrading attacks. Adding security headers and pinning the certificates can also enhance security. Here is more on that:

> **[Fixing your Web Server's Security Headers: From Hall of Shame to Hall of Fame -...](https://www.cloudinsidr.com/content/fixing-your-web-servers-security-headers-from-hall-of-shame-to-hall-of-fame/)**
>
> \[Updated 2017-11-25\] This post explains how to set robust security headers in NGINX to defend your web application from malicious payloads and other forms of attacks. Choose your HTTP headers wisely. In order to effectively prevent (or sufficiently...

Glad you liked the other article:-) Hopefully this one will also be of help. Should you have any questions, please feel free to shoot a comment!

---

<div class="post-metadata">

**Author:** ![CloudInsidr](https://sea3.discourse-cdn.com/letsencrypt/user_avatar/community.letsencrypt.org/cloudinsidr/32/33154_2.png) [@CloudInsidr](https://community.letsencrypt.org/u/CloudInsidr)\
**Post date:** [March 18, 2016, 1:12am UTC](https://community.letsencrypt.org/t/replacing-apache-with-nginx-for-http2-support/12674/17 "2016-03-18T01:12:53Z")

</div>

Hi Dominik,

With NGINX-specific questions (not related to Letsencrypt), feel free to join the official NGINX group:

[https://www.linkedin.com/groups/2000893](https://www.linkedin.com/groups/2000893)

and we will do our best to ease your transition:-)  
Also, please link to [CloudInsidr.com](http://CloudInsidr.com) so others may find helpful information.

Anna E. Kobylinska & Filipe Martins

> **[Cloud Insidr - Cybersecurity in the Age of the Machine](https://www.cloudinsidr.com/content/)**
>
> Cybersecurity in the Age of the Machine

---

<div class="post-metadata">

**Author:** ![CloudInsidr](https://sea3.discourse-cdn.com/letsencrypt/user_avatar/community.letsencrypt.org/cloudinsidr/32/33154_2.png) [@CloudInsidr](https://community.letsencrypt.org/u/CloudInsidr)\
**Post date:** [March 18, 2016, 2:27am UTC](https://community.letsencrypt.org/t/replacing-apache-with-nginx-for-http2-support/12674/18 "2016-03-18T02:27:06Z")

</div>

Hi Dominik,

 ![](https://global.discourse-cdn.com/letsencrypt/original/2X/5/5fd7f55e63ad7362718fb369134d10fa528fa4f0.png)

We also implemented HTTP Public Key Pinning (HPKP) as you can see in the attached screenshot and described in:

" **Pin your public keys**

The Public Key Pinning Extension for HTTP (HPKP) tells a web client to associate a specific cryptographic public key with a certain web server so as to prevent MITM (man-in-the-middle) attacks with forged certificates.(…)"

Here is to do it:

> **[Fixing your Web Server's Security Headers: From Hall of Shame to Hall of Fame...](https://www.cloudinsidr.com/content/fixing-your-web-servers-security-headers-from-hall-of-shame-to-hall-of-fame/#more-916)**
>
> \[Updated 2018-06-10\] This post explains how to set up robust security headers in NGINX to protect your web application from malicious payloads and other forms of attacks. Choose your HTTP(S) headers wisely. In order to effectively prevent (or...

Filipe

---

<div class="post-metadata">

**Author:** ![kelunik](https://sea3.discourse-cdn.com/letsencrypt/user_avatar/community.letsencrypt.org/kelunik/32/359_2.png) [@kelunik](https://community.letsencrypt.org/u/kelunik)\
**Post date:** [March 18, 2016, 7:07am UTC](https://community.letsencrypt.org/t/replacing-apache-with-nginx-for-http2-support/12674/19 "2016-03-18T07:07:17Z")

</div>

There’s no `HSTS` header sent. Did you forget to reload your server config after adding the header?

---

<div class="post-metadata">

**Author:** ![dominikwilkowski](https://sea3.discourse-cdn.com/letsencrypt/user_avatar/community.letsencrypt.org/dominikwilkowski/32/6003_2.png) [@dominikwilkowski](https://community.letsencrypt.org/u/dominikwilkowski)\
**Post date:** [March 18, 2016, 9:50pm UTC](https://community.letsencrypt.org/t/replacing-apache-with-nginx-for-http2-support/12674/20 "2016-03-18T21:50:57Z")

</div>

Hey

I did but I still have issues with the header. I think @CloudInsidr is right, we need to take this into a NGINX forum rather than here as it has nothing to do with letsencrypt.

Thanks guys for the help.

I’ll post a follow up once I get it all sorted to close this off here.

[Next page](https://community.letsencrypt.org/t/replacing-apache-with-nginx-for-http2-support/12674.md?page=2)
