ERR_EMPTY_RESPONSE with Ghost 0.7.5, Nginx 1.9, HTTPS and HTTP/2

ERR_EMPTY_RESPONSE with Ghost 0.7.5, Nginx 1.9, HTTPS and HTTP/2

When I hit, I get
"No data received ERR_EMPTY_RESPONSE".

When I hit, I get the site.    
ghost 0.7.5
nginx 1.4 => 1.9.9
letsencrypt 0.2.0   
Digital Ocean: Ubuntu 14.04 Ghost 1-click droplet  
Under Networking > Domains, I have both and as A records pointing to the server's IP address (@).
DNSimple records 

| Type  | Name                  | TTL               | Content                   |
|------ |---------------------  |---------------    |------------------------   |
| URL   |   | 3600 (1 hour)     |    |  

The only modified portion in Ghost's config.js is my domain.  
url: '',

Nginx 1.9
nginx 1.9 doesn't create the following directories by default:
and the usual default conf isn't created in either of those directories.
Instead, there is a etc/nginx/conf.d/default.conf and the important one, etc/nginx/conf.d/nginx.conf. You'll see a lot of tutorials telling you to delete the default.conf, which seems to be fine, but whatever you do, do NOT delete nginx.conf.
Also, you should move/create your ghost.conf into the /etc/nginx/conf.d/ directory. That's what fixed one of my problems, because the last line in etc/nginx/conf.d/nginx.conf looks in the /conf.d/ directory an includes any files there: include /etc/nginx/conf.d/*.conf; 
Here's my /etc/nginx/conf.d/ghost.conf file:  
  server {
  root /usr/share/nginx/html;
  index index.html index.htm;

  listen 443 ssl http2;

    ssl_certificate /etc/letsencrypt/live/;
    ssl_certificate_key /etc/letsencrypt/live/;
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_prefer_server_ciphers on;

  location ~ /.well-known {
      allow all;
      root /var/www/;

  location / {
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header HOST $http_host;
        proxy_set_header X-NginX-Proxy true;

        proxy_redirect off;
        root /var/www/;
      location /.well-known/ {
        root /var/www/;

server {
    listen 80 ssl http2;
    return 301 https://$host$request_uri;

Now, I had this all working fine, and tried to upgrade nginx to 1.9+, in order to serve via http/2. DigitalOcean's 1-click Ghost droplet defaults to using nginx 1.4. 
Long story short, I kept getting this error:  
dpkg: error processing archive /var/cache/apt/archives/nginx_1.9.9-1~trusty_amd64.deb (--unpack):

and the only solution I found was   
apt-get purge nginx nginx-common

I was then able to install nginx 1.9, by adding the following lines to my /etc/apt/source.list file.
deb trusty nginx
deb-src trusty nginx

Now I simply added listen 80 ssl http2; and listen 443 ssl http2;, and http/2 works fine. But only when the https:// URL is explicitly entered. 
I found some evidence that points to the fact that express doesn't support http/2, but I'm not 100% on that. 
Any ideas would be much appreciated. 


Solution 1:

I don’t think this is a problem with Ghost, nor with the DNS.

You can exclude the DNS because both www and not-www are resolving to the configured IP.

➜  ~  dig +short
➜  ~  dig +short

The DNS protocol works at lowel level than HTTP and it doesn’t care about the HTTP version or whether you use HTTP vs HTTPS. Therefore we can exclude the DNS and move to examine the higher level protocols.

I also exclude the issue is Ghost/Express, because I can send an HTTP/2 request to your blog when I use HTTPS.

➜  ~  curl --http2 -I
HTTP/2.0 200
date:Sun, 24 Jan 2016 19:34:30 GMT
content-type:text/html; charset=utf-8
cache-control:public, max-age=0

I can also fallback to HTTP 1.1, as long as I use the HTTP version of the site.

➜  ~  curl -I
HTTP/1.1 200 OK
Server: nginx/1.9.9
Date: Sun, 24 Jan 2016 19:35:36 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 13594
Connection: keep-alive
X-Powered-By: Express
Cache-Control: public, max-age=0
ETag: W/"351a-fflrj9kHHJyvRRSahEc8JQ"
Vary: Accept-Encoding

Hence, the problem is the Nginx configuration. Specifically, the problem is the Nginx configuration for the HTTP-only block.

I can’t try it right now, but personally I believe the problem is this line:

listen 80 ssl http2;

it should be

listen 80;

The ssl directive is used to enforce the listening socket to understand ssl. However, in your case it doesn’t make sense to have the socket listening on 80 to use HTTPS. Moreover, the socket that uses ssl must have an associated SSL configuration declared (aka at least a valid certificate and key).

Generally, you use ssl to configure a single server that handles both HTTP and HTTPS requests:

server {
    listen              80;
    listen              443 ssl;

Also note that as explained in the Nginx documentation

The use of the ssl directive in modern versions is thus discouraged.

The use of http2 in combination with a non-https socket can also be the cause of trouble.

Quoting this article:

While the spec doesn’t force anyone to implement HTTP/2 over TLS but allows you to do it over clear text TCP, representatives from both the Firefox and the Chrome development teams have expressed their intents to only implement HTTP/2 over TLS. This means HTTPS:// URLs are the only ones that will enable HTTP/2 for these browsers.

Therefore, assuming it would be possible, serving the non-https site via HTTP/2 may not be useful. Actually, I doubt it’s even possible as of today, given the issue described in this ticket which seems to match your issue.

To summarize, simply change

server {
    listen 80 ssl http2;
    return 301 https://$host$request_uri;


server {
    listen 80;
    return 301 https://$host$request_uri;