Search code examples
nginxhttpshttp2ghostlets-encrypt

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


Problem

When I hit kevinsuttle.com, I get

"No data received ERR_EMPTY_RESPONSE".

When I hit https://kevinsuttle.com, 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 kevinsuttle.com and www.kevinsuttle.com as A records pointing to the server's IP address (@).

DNSimple records

| Type  | Name                  | TTL               | Content                   |
|------ |---------------------  |---------------    |------------------------   |
| URL   | www.kevinsuttle.com   | 3600 (1 hour)     | http://kevinsuttle.com    |  

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

url: 'http://kevinsuttle.com',

Nginx 1.9

nginx 1.9 doesn't create the following directories by default:
/etc/nginx/sites-available
/etc/nginx/sites-enabled

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;

  server_name kevinsuttle.com www.kevinsuttle.com;
    ssl_certificate /etc/letsencrypt/live/kevinsuttle.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/kevinsuttle.com/privkey.pem;
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_prefer_server_ciphers on;
    ssl_ciphers 'EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH';

  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_pass http://127.0.0.1:2368;
        proxy_redirect off;
        root /var/www/;
    }
      location /.well-known/ {
        root /var/www/;
    }
}

server {
    listen 80 ssl http2;
    server_name kevinsuttle.com;
    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 http://nginx.org/packages/mainline/ubuntu/ trusty nginx
deb-src http://nginx.org/packages/mainline/ubuntu/ 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

  • 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 kevinsuttle.com +short
    162.243.4.120
    ➜  ~  dig www.kevinsuttle.com +short
    162.243.4.120
    

    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 https://kevinsuttle.com/
    HTTP/2.0 200
    server:nginx/1.9.9
    date:Sun, 24 Jan 2016 19:34:30 GMT
    content-type:text/html; charset=utf-8
    content-length:13594
    x-powered-by:Express
    cache-control:public, max-age=0
    etag:W/"351a-fflrj9kHHJyvRRSahEc8JQ"
    vary:Accept-Encoding
    

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

    ➜  ~  curl -I https://kevinsuttle.com/
    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;
        server_name         www.example.com;
        ssl_certificate     www.example.com.crt;
        ssl_certificate_key www.example.com.key;
        ...
    }
    

    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;
        server_name kevinsuttle.com;
        return 301 https://$host$request_uri;
    }
    

    to

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