NGINX redirect everything except letsencrypt to https

I have a simple config, redirect everything except letsencrypt requests to https, and then have my virtual hosts only on https..

Currently all my requests are redirected to https, and then a 404 for letsencrypt:

This is my config...

server {
        listen 80 default_server;
        listen [::]:80 default_server;
        server_name _;
        location ^~ /.well-known/acme-challenge/ {
            allow all;
            default_type text/plain;
            return 200 "$1.abcd-efgh";
        }

        location / {
            return 301 https://$host$request_uri;
        }
}

server {
    listen 443 ssl;
    server_name plex.my_domain.com;

    ssl_session_timeout 30m;
    ssl_protocols TLSv1.2 TLSv1.1 TLSv1;
    ssl_certificate      /root/.acme.sh/plex.my_domain.com/fullchain.cer;
    ssl_certificate_key  /root/.acme.sh/plex.my_domain.com/plex.my_domain.com.key;
    ssl_session_cache shared:SSL:10m;


    add_header X-Xss-Protection "1; mode=block" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header Strict-Transport-Security "max-age=2592000; includeSubdomains" always;
    add_header X-Frame-Options "SAMEORIGIN" always;
    proxy_hide_header X-Powered-By;
    add_header 'Referrer-Policy' 'no-referrer';
    add_header Content-Security-Policy "frame-ancestors my_domain.com plex.my_domain.com;";


    location / {
        proxy_pass http://127.0.0.1:32400;

        proxy_set_header Range $http_range;
        proxy_set_header If-Range $http_if_range;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

        #Next three lines allow websockets
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}

You have HSTS headers set in your https server block. This means that if you visit your site with https once with your browser, your browser will always connect to your domain with https after the first visit.

This means that you cannot test your configuration with a browser. You need to test it with curl or similar tool that doesn't store HSTS lists.

There is a minor tweak for your configuration, you can use a simple prefix match location /.well-known/acme-challenge for your LetsEncrypt location. nginx will use the most specific match from the location blocks.


Well, wasn't the HSTS in my case

server {
    # ...
    location /.well-known/acme-challenge/ {
        # put your configuration here, if needed
    }
    location / {
        return 301 https://$server_name$request_uri;
    }
}

Summary: put the 301 redirect inside the / location


You can create a snippet to use in your config in /etc/nginx/snippets.d/letsencrypt

location ~ /\.well-known/acme-challenge/ {
  allow all;
  default_type "text/plain";
  root /usr/share/nginx/html;
  try_files $uri =404;
  break;
} 

then include it in your location:

server {
  listen 80;
  server_name *.yourdomain.org yourdomain.org;
  location / {
    return 301 https://$host$request_uri;
  }
}

server {
  listen 443 ssl;
  server_name *.yourdomain.org yourdomain.org;
  include snippets.d/letsencrypt;
  ssl_certificate     /etc/letsencrypt/live/yourdomain.org/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/yourdomain.org/privkey.pem;
  location / {
      # your config for the domain goes here
  }
}
  1. Make sure, the 301 redirect is inside a location block (otherwise it will be called everytime).

  2. Make sure, the redirect leads to a whole domain and not a subdirectory of another domain (like https://yourdomain.org/subdir$request_uri) so the well-known challenge is routed correctly.