nginx order of server_name rewrite rules - nginx

I'm playing around with nginx rewrite rules using server and server_name. The documentation sounds simple and clear enough about the order of precedence, but I'm getting some slightly odd behaviour and want to check whether I missed anything.
I am trying to redirect anything other than www.domain.com (e.g. www.domain.net, www.domain.info) to www.domain.com. With the exception of www.domain.de to redirecto to www.domain.com/de.
I have the following rules:
server {
server_name domain.de www.domain.de;
rewrite ^(.*) http://www.domain.com/de$1 permanent;
}
server {
server_name _;
rewrite ^(.*) http://www.domain.com$1 permanent;
}
server {
listen 80;
server_name localhost domain.com www.domain.com;
...
}
However, it seems like with this ruleset, it will always redirect all non .com domains to www.domain.com/de. Whereas if I flip the first two server segments it works fine.
Am I doing something wrong? Why is the order of the rules important if the server names are explicitly specified?

The right configuration would be:
server {
listen 80;
server_name domain.de www.domain.de;
return 301 http://www.domain.com/de$request_uri;
}
server {
listen 80 default_server;
server_name _;
return 301 http://www.domain.com$request_uri;
}
server {
listen 80;
server_name "" localhost domain.com www.domain.com;
...
}
server_name _; is just a popular stub. The default value of the server_name directive is "" which handles requests without "Host" header. If client doesn't send it at all then server_name "" will leads to redirection loop in a configuration like yours.
Please, take a look at:
http://nginx.org/en/docs/http/request_processing.html
http://nginx.org/en/docs/http/server_names.html
http://nginx.org/en/docs/http/converting_rewrite_rules.html

Using server_name _; to mean 'this is the default server' is a common mistake. It has no special meaning, and you need to use the default_server flag on the listen directive to mark that second server as default:
server {
listen 80 default_server;
server_name _;
rewrite ^ http://www.domain.com$request_uri? permanent;
}

Related

conditional nginx rewrite rule that exclude redirecting to www if www is already in request

I am using nginx for managing multiple domains for reverse proxy.
I want to redirect all http request to https
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
return 301 https://$host$request_uri;
}
However, I am not sure how can I make it work for rewriting non www requests to redirect to www in generic way excluding rewriting those requests that already has www in requests as if I just modify below rule it creates problem when someone write as www.domain.com it
rewrite to www.www.domain.com
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
return 301 https://www.$host$request_uri;
}
Please check this image to understand the issue.
So I need to have some condition that handles that.
Please try this
server {
listen 80;
listen [::]:80;
server_name yourdomain.com www.yourdomain.com me.yourdomain.com;
return 301 https://yourdomain.com$request_uri;
}
this will rewrite all 3 domains to https://yourdomain.com
I am not sure about my answer. I am not able to test this now.
please try this and comment back
If you want a generic approach, you can use map directive (should be placed outside the server block):
map $host $basename {
~^www\.(.+) $1;
default $host;
}
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
return 301 https://$basename$request_uri;
}
Update 1
It's look I misread your question (you need to redirect example.com to www.example.com but my answer does an opposite thing), please try the following:
map $host $basename {
~^www\.(.+) $host;
default www.$host;
}
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
return 301 https://$basename$request_uri;
}
Update 2
OP asks an additional question:
Is there a way to exclude specific sub domain like static.example.com which I don't want to rewrite to www.static.example.com?
Yes, rewrite of static subdomain can be prevented with the following map block:
map $host $basename {
~^(?:www|static)\..+ $host;
default www.$host;
}
If you want to prevent rewrite of any three-component domain name, you can use
map $host $basename {
~[^.]+\.[^.]+\.[^.]+ $host;
default www.$host;
}
Non-www to www & https redirect
If I understand correctly, then you want to force non www to www. And you want to force all requests to https. This config code checks if the $host start with "www." or not. If it does not, then nginx will add "www." before the $host then return the correct https location. It will work with subdomains as well.
server {
if ($host !~* ^www\.) {
# if host name starts with (case insensitive) "www."
return 301 https://www.$host$request_uri;
}
if ($host ~* ^www\. ) {
# if host name doesn't start with (case insensitive) "www."
return 301 https://$host$request_uri;
}
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
return 404;
}
Possibly better way with Certbot
You can automatically produce the code you need by using Certbot. It will also help you manage certificates a lot and automate renewal. But Certbot will not change non-www to www, you'll have to edit auto-generated config to look like the config above. The other difference is that you will be expected to have different config blocks/files per domain, but not sure if necessary. This is what the default config looks like per domain.
server {
if ($host = www.yourdomain.com) {
return 301 https://$host$request_uri;
} # managed by Certbot
if ($host = yourdomain.com) {
return 301 https://$host$request_uri;
} # managed by Certbot
listen 80;
listen [::]:80;
server_name yourdomain.com www.yourdomain.com;
return 404; # managed by Certbot
}
I think it's best practice to separate the config files for each domain and subdomain and use the default Certbot format until you have understood Nginx more. This way you can disable the redirect of non-www to www very easily. The default code above from Certbot, would be what you would put when you don't want to redirect in that case. Long term this makes more sense to me, but I understand when users are more comfortable with www or other parts of the website might need the www.

Too many redirects - changing naked url -> www.example.com

I've changed my conf file so that when a user types in the domain without www it redirects to the domain with www:
server_name example.com;
return 301 $scheme://www.example.com$request_uri;
I also wish for my https for anything under /user
I get the error of too may redirects, where am I going wrong?
So I have:
server {
listen 80 default_server;
listen [::]:80 default_server ipv6only=on;
root /var/www/example.com/site;
index index.html index.htm;
# Make site accessible from http://localhost/
server_name example.com;
return 301 $scheme://www.example.com$request_uri;
location / {
# First attempt to serve request as file, then
# as directory, then fall back to displaying a 404.
try_files $uri $uri/ =404;
# Uncomment to enable naxsi on this location
# include /etc/nginx/naxsi.rules
}
location /user {
rewrite ^ https://$http_host$request_uri? permanent;
}
}
For port 443:
server {
listen 443;
server_name example.com;
return 301 $scheme://www.example.com$request_uri;
root /var/www/example.com/site;
index index.html index.htm;
ssl on;
ssl_certificate //path here
ssl_certificate_key //path here
location / {
rewrite ^ http://$http_host$request_uri? permanent;
}
location /user {
}
}
With
listen 80 default_server;
you are telling nginx that this server block is the default server for all http requests regardless of the server name.
The directive
return 301 $scheme://www.example.com$request_uri;
sets nginx to redirect all incoming traffic for this server block to www.example.com. That redirected traffic hits the same server block again (default server) and the process repeats itself, hence a redirect loop.
To fix this change your config file and add a second server block:
server {
listen 80;
listen [::]:80 ipv6only=on;
server_name www.example.com;
#rest of your config
[...]
}
server {
server_name example.com;
return 301 $scheme://www.example.com$request_uri;
}
Port 80 can be left out, as it is the default, in case you are wondering.
The same principle applies to traffic for 443 (in the redirect block you however must give the port and ssl specific configuration).

Nginx URL masking to a different domain

There's a few similar questions on SO, but none exactly mine, and I've had no luck trying to adapt their answers so far.
I want to map the URL http://sub.example.com to https://123.12.12.12/path, such that the browser still shows the URL http://sub.example.com.
My Nginx config file looks like,
server {
listen 80;
server_name sub.example.com;
location / {
proxy_pass https://123.12.12.12;
rewrite ^/$ /path last;
}
}
The routing works here, but the URL displayed is http://sub.example.com/path. How do I make it display only http://sub.example.com?
server {
listen 80;
server_name sub.example.com;
location / {
proxy_pass https://123.12.12.12/path;
}
}
Thats how it works. If proxy_pass contains locations part - current location will be replaced to specified. http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_pass
But it's help only for http request and http redirects. If application create html with links https://123.12.12.12 - it's still unchanged. In this case you can try ngx_http_sub_module.
I did like this:
server {
listen 80;
listen [::]:80;
listen 443 http2 ssl;
listen [::]:443 http2 ssl;
server_name domain1;
if ($request_method ~* OPTIONS|GET|HEAD) {
return 301 https://domain2$request_uri;
}
location ~* api {
proxy_pass https://domain2$request_uri;
}
}
Because post-requests will cause a 405 error when redirecting.

Nginx convert subdomain to path component without redirect

The idea is to take incoming requests to http://abc.example.com/... and rewrite them to http://example.com/abc/...
That's easy enough to do with a 301/302 redirect:
# rewrite via 301 Moved Permanently
server {
listen 80;
server_name abc.example.com;
rewrite ^ $scheme://example.com/abc$request_uri permanent;
}
The trick is to do this URL change transparently to the client when abc.example.com and example.com point at the same Nginx instance.
Put differently, can Nginx serve the contents from example.com/abc/... when abc.example.com/... is requested and without another client round trip?
Starting Point Config
Nginx config that accomplishes the task with a 301:
# abc.example.com
server {
listen 80;
server_name abc.example.com;
rewrite ^ $scheme://example.com/abc$request_uri permanent;
}
# example.com
server {
listen 80;
server_name example.com;
location / {
# ...
}
}
# abc.example.com
server {
listen 80;
server_name abc.example.com;
location / {
proxy_pass http://127.0.0.1/abc$request_uri;
proxy_set_header Host example.com;
}
}

nginx redirect HTTPS to HTTP

How can i redireect from https to http?
i have the code below but it does not seem to work.
server {
listen 443;
server_name example.com;
rewrite ^(.*) http://example.com$1 permanent;
}
The answer above will work, you need to generate a self signed cert (or have a real one) and configure nginx as such:
server {
listen *:443;
ssl on;
server_name domain.com;
rewrite ^(.*) http://domain.com$1 permanent;
ssl_certificate /data/certs/domain.crt;
ssl_certificate_key /data/certs/domain.key;
}
Keep in mind, if it is a self signed cert the browser will give you an ugly warning.
Building off jberger's comment a configuration that should work would be:
server {
listen *:80;
server_name example.com;
}
server {
listen *:443 ssl;
server_name example.com;
ssl_certificate /etc/ssl/certs/example.com.cert;
ssl_certificate_key /etc/ssl/private/example.com.key;
return 301 http://$server_name$request_uri;
}
if ($host = 'foo.com') {
rewrite ^/(.*)$ http://www.foo.com$1 permanent;
}
You need to create 2 separate server blocks:
Port 443 (HTTPS) - Define everything like PHP, 404, home, root etc in this block. Even if you want to redirect https://www.example.com to https://example.com or vice-versa, do it over here as #coulix has done.
Port 80 (HTTP) - In here you will just use:
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
# Redirect HTTP to HTTPS
return 301 https://example.com$request_uri;
}

Resources