3

I've spent the last few hours trying to understand what is happening with the nginx location documentation and questions by other people on serverfault (like this one). From everything I gathered, my solution should work, but it just doesn't.

So as the title says, I'd like to configure nginx such that it will serve the challange file that acme.sh creates, but redirect everything else to a specific https domain of mine configured in the same server (this one works perfectly fine).

My server block:

server { listen 80 default_server; listen [::]:80 default_server; server_name _; root /usr/share/nginx/html; location ^~ /.well-known/acme-challenge/ { try_files $uri /; } location / { return 301 https://example.com$request_uri; } } 

For testing purposes, I've created a file myself in this directory: /usr/share/nginx/html/.well-known/acme-challange/somefile. The testing curl command: curl http://example.com/.well-known/acme-challange/somefile

Problem is: No matter which request, everything is always redirected. The moment I remove the / location block, however, the curl command will give me the contents of my test file. Obviously no redirects will be done in that case.

From the nginx documentation, my understanding is with the modifier I put, once the acme prefix matches (which it obviously does since it works when it is the only location block), it should not try to match anything else. I've also tried simply removing the try_files directive, but it makes no difference. Instead of return 301 ... I've also tried to do a rewrite, which also has the exact same behavior: There will always be a redirect no matter which URI is used.

For some reason the / location block is always used once it's there, even if there are more specific locations that match and even if said locations have the ^~ modifier.

Another variant I just tried:

server { listen 80 default_server; listen [::]:80 default_server; server_name _; root /usr/share/nginx/html; location /.well-known/acme-challenge/ { try_files $uri =404; } return 301 https://example.com$request_uri; } 

Very similar behavior: If the return is present -> redirect. If I comment it out: my test file is served to me with the curl call. One curious thing is that I cannot move the root directive inside the location blocks. If I do, nginx starts to look for files in /etc/nginx/html, which is apparently the default root, and will throw an error in the logs ("no such file or directory") when I do the http request.

Btw, I did not remove anything from this server block, I just changed the domain name to example.com. It is also the only server block listening on port 80 and no other configs are included in the file. Of course, after every change, I also restarted the nginx service. If it matters, my nginx version is 1.18.0

Output of nginx -T as requested in comments (domain name was changed and mime.types file was cut due to character limits here):

2021/04/23 02:21:00 [warn] 88558#88558: could not build optimal types_hash, you should increase either types_hash_max_size: 1024 or types_hash_bucket_size: 64; ignoring types_hash_bucket_size nginx: the configuration file /etc/nginx/nginx.conf syntax is ok nginx: configuration file /etc/nginx/nginx.conf test is successful # configuration file /etc/nginx/nginx.conf: user html; worker_processes auto; #error_log logs/error.log; #error_log logs/error.log notice; #error_log logs/error.log info; #pid logs/nginx.pid; events { worker_connections 1024; } http { include mime.types; default_type application/octet-stream; ssl_session_cache shared:SSL:10m; ssl_session_timeout 10m; ssl_prefer_server_ciphers on; ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES128+EECDH:AES256+EECDH:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:AES256+EDH:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256"; ssl_protocols TLSv1.1 TLSv1.2 TLSv1.3; ssl_dhparam /etc/ssl/certs/dhparam.pem; ssl_ecdh_curve secp384r1:prime256v1; #log_format main '$remote_addr - $remote_user [$time_local] "$request" ' # '$status $body_bytes_sent "$http_referer" ' # '"$http_user_agent" "$http_x_forwarded_for"'; #access_log logs/access.log main; sendfile on; #tcp_nopush on; keepalive_timeout 65; gzip on; # for certificate verification server { listen 80 default_server; listen [::]:80 default_server; server_name _; root /usr/share/nginx/html; location /.well-known/acme-challenge/ { try_files $uri =404; } #return 301 https://example.com$request_uri; } server { listen 443 ssl http2; listen [::]:443 ssl http2; server_name example.com; ssl_certificate ssl/example.com.crt; ssl_certificate_key ssl/example.com.key; #access_log logs/host.access.log main; root /usr/share/nginx/html; index index.html index.php; location / { fastcgi_param SCRIPT_FILENAME $document_root/src/index.php; include fastcgi_params; # override SCRIPT_NAME which was set in fastcgi_params fastcgi_param SCRIPT_NAME /src/index.php; fastcgi_pass unix:/var/run/php-fpm/php-fpm.sock; } location /public/ { allow all; } #error_page 404 /404.html; # redirect server error pages to the static page /50x.html # #error_page 500 502 503 504 /50x.html; #location = /50x.html { # root /usr/share/nginx/html; #} } } # configuration file /etc/nginx/mime.types: types { #REMOVED BECAUSE OF CHARACTER LIMIT } # configuration file /etc/nginx/fastcgi_params: fastcgi_param QUERY_STRING $query_string; fastcgi_param REQUEST_METHOD $request_method; fastcgi_param CONTENT_TYPE $content_type; fastcgi_param CONTENT_LENGTH $content_length; fastcgi_param SCRIPT_NAME $fastcgi_script_name; fastcgi_param REQUEST_URI $request_uri; fastcgi_param DOCUMENT_URI $document_uri; fastcgi_param DOCUMENT_ROOT $document_root; fastcgi_param SERVER_PROTOCOL $server_protocol; fastcgi_param REQUEST_SCHEME $scheme; fastcgi_param HTTPS $https if_not_empty; fastcgi_param GATEWAY_INTERFACE CGI/1.1; fastcgi_param SERVER_SOFTWARE nginx/$nginx_version; fastcgi_param REMOTE_ADDR $remote_addr; fastcgi_param REMOTE_PORT $remote_port; fastcgi_param SERVER_ADDR $server_addr; fastcgi_param SERVER_PORT $server_port; fastcgi_param SERVER_NAME $server_name; # PHP only, required if PHP was built with --enable-force-cgi-redirect fastcgi_param REDIRECT_STATUS 200; 
4
  • Could you add the output of: nginx -T ? Remove/edit any sensitive data. E.g. the website should be called www.example.com Commented Apr 23, 2021 at 0:12
  • @MirceaVutcovici added. Had to remove the mime.types stuff tho due to size restrictions here. But that shouldn't be relevant as it's the stock file delivered by the nginx package on arch. Commented Apr 23, 2021 at 0:31
  • 1
    FYI - your first server block example does not work because the slash in the return location block is a prefix match which takes precedence over the ^~ non-regular expression match, thus the letsencrypt location block is never selected and the return is always executed. The second one fails because the return is at the server level and thus takes precedence over all location blocks - again, the letsencrypt location block is never selected. Commented Sep 10, 2022 at 21:49
  • Related: serverfault.com/questions/1017545/… Commented Dec 11, 2022 at 18:24

1 Answer 1

4

I made this to work with the following vhost configuration:

server { listen 80; server_name example.com; location /.well-known/ { root /whatever/i/specified/in/certbot/-d/argument; } location / { return 301 https://$host$request_uri; } } 

(the SSL vhost configuration is not important here)

What is different from yours is that I have root inside location. This setup works very well on multitude of reverse proxy and origin servers.

2
  • 1
    This worked! Now, do you have any idea why this works and my attempts above don't? I'd really like to know for future reference. Commented Apr 24, 2021 at 12:50
  • 1
    @Anpan refer to the Pitfalls and Common Mistakes page - Incorrect return context section. When a return directive is placed at a server level, it takes precedence over all location blocks and thus the letsencrypt location never gets selected. By wrapping the return in a location block, all location blocks are evaluated for the best match and letsencrypt will work because it uses a more specific match than the slash. Commented Sep 10, 2022 at 21:43

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.