I have nginx as a proxy in front of my app to terminate TLS. I have it set to listen to port 80 and redirect to 443.

Originally Chromium would download a file named "download" instead of redirecting but for some reason it's not doing that anymore (I tried what was suggested in this question but it didn't help). While Chromium redirects properly even though the problem clearly still exists, and Firefox still reacts to this by assuming it is a file download:

firefox offering to download the binary data

The problem is most visible with curl:

curl -v http://example.com
* Rebuilt URL to: http://example.com/
*   Trying
* Connected to example.com ( port 80 (#0)
> GET / HTTP/1.1
> Host: example.com
> User-Agent: curl/7.50.0
> Accept: */*
* Connection #0 to host example.com left intact

Instead of returning the usual headers/status/etc. it appears to be sending back binary data. Meanwhile the https version seems to work correctly:

curl -v https://example.com
* Rebuilt URL to: https://example.com/
*   Trying
* Connected to example.com ( port 443 (#0)
* ALPN, offering http/1.1
* Cipher selection: ALL:!EXPORT:!EXPORT40:!EXPORT56:!aNULL:!LOW:!RC4:@STRENGTH
* successfully set certificate verify locations:
*   CAfile: /etc/ssl/certs/ca-certificates.crt
  CApath: none
* TLSv1.2 (OUT), TLS header, Certificate Status (22):
* TLSv1.2 (OUT), TLS handshake, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS change cipher, Client hello (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS change cipher, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-RSA-AES128-GCM-SHA256
* ALPN, server accepted to use http/1.1
* Server certificate:
*  subject: CN=example.com
*  start date: Jul 29 18:50:00 2016 GMT 
*  expire date: Oct 27 18:50:00 2016 GMT 
*  subjectAltName: host "example.com" matched cert's "example.com"
*  issuer: C=US; O=Let's Encrypt; CN=Let's Encrypt Authority X3
*  SSL certificate verify ok. 
> GET / HTTP/1.1
> Host: example.com
> User-Agent: curl/7.50.0
> Accept: */* 
< HTTP/1.1 200 OK
< Server: nginx/1.11.1
< Date: Fri, 29 Jul 2016 22:53:33 GMT 
< Content-Type: text/html; charset=utf-8
< Content-Length: 1121
< Connection: keep-alive
< X-Powered-By: Express
< ETag: W/"461-VBuWbiWQQ/3ptwQjG8pM3w"
< Strict-Transport-Security: max-age=63072000; includeSubdomains; preload
< X-Frame-Options: SAMEORIGIN
< X-Content-Type-Options: nosniff
<!DOCTYPE html>
    <meta charset="utf-8">

Clearly my nginx is misconfigured, but it's not clear to me how.

My nginx.conf:

user nginx;
worker_processes 2;

events {
    worker_connections  8096;
    multi_accept        on;
    use                 epoll;

http {
  include mime.types;
  default_type application/octet-stream;

  proxy_cache_path /var/cache/nginx keys_zone=anonymous:10m;
  proxy_temp_path /var/tmp/nginx;

  sendfile on;
  client_max_body_size 20M;
  tcp_nodelay off;
  tcp_nopush on;
  keepalive_timeout 65;

  access_log /var/log/nginx/access.log;
  error_log /var/log/nginx/error.log;

  map $http_upgrade $connection_upgrade {
      default upgrade;
      ''      close;

  #Include the vhost files.
  include vhosts/*.conf;

And the vhosts file:

server {
  listen 80 http2;
  server_name ${DOMAIN};
  return 301 https://$server_name$request_uri;

server {
  listen 443 ssl http2;
  server_name ${DOMAIN};

  ssl_certificate /etc/letsencrypt/live/${PATH}/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/${PATH}/privkey.pem;
  ssl_dhparam /etc/ssl/dhparams.pem;

  ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
  ssl_prefer_server_ciphers on;
  ssl_session_cache shared:SSL:10m;
  add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload" always;
  add_header X-Frame-Options SAMEORIGIN;
  add_header X-Content-Type-Options nosniff;
  ssl_session_tickets off;
  ssl_stapling on;
  ssl_stapling_verify on;

  root /etc/letsencrypt/webrootauth;

  location / {
    proxy_pass http://${UPSTREAM};
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $remote_addr;
    proxy_cache   anonymous;
    proxy_cache_valid 200 301 302 30m;
    expires 30m;
    proxy_buffering off;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $http_connection;      

  location ~* \.(html|css|jpg|gif|ico|js)$ {
    proxy_cache          cache;
    proxy_cache_key      $host$uri$is_args$args;
    proxy_cache_valid    200 301 302 30m;
    expires              30m;
    proxy_pass  http://backend;

  location /.well-known/acme-challenge {
    alias /etc/letsencrypt/webrootauth/.well-known/acme-challenge;
    location ~ /.well-known/acme-challenge/(.*) {
      add_header Content-Type application/jose+json;

Does anyone see what could be causing this?

  • What should http2 do in your listen 80 directive? Try to remove this. – gf_ Jul 29 '16 at 23:30
  • It enables support for HTTP V2 (nginx.org/en/docs/http/ngx_http_v2_module.html). Removing it does not solve the problem. – mikewilliamson Jul 29 '16 at 23:40
  • What happens if you disable http to https forwarding, set up the same configuration under http, and request the same resources? – Tim Jul 29 '16 at 23:42
  • It does the same thing. – mikewilliamson Jul 30 '16 at 0:00
  • 2
    serverfault.com/q/779594/211028 remove http2 from all server blocks that listen to port 80 – Alexey Ten Jul 30 '16 at 6:04

The http2 directive you're using implies that the port has SSL support (it doesn't work without SSL), so the binary data you see is actually nginx trying to communicate securely over port 80 without having any certificate.

You can verify that with:

curl --tlsv1.2 https://your-domain-name.is:80

which gets you an:

curl: (35) error:140770FC:SSL routines:SSL23_GET_SERVER_HELLO:unknown protocol

while any other server will give you a

curl: (35) Unknown SSL protocol error in connection to example.com:80

remove http2 directive from port 80 (and put it to port 443)

If you insist to keep the http2 directive on port 80, at least add all ssl-related parameters to that section, so you would be able to communicate via ssl using https://your-domain-name.is:80 (just kidding...)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.