How to use nginx if's param $1 in rewrite statement - nginx

I have this working code in nginx config:
if ($http_host ~* ^www\.(.+)$) {
set $host2 $1;
rewrite (.*) http://$host2$1;
}
I think that string set $host2 $1; may be omitted and $1 used in rewrite statement without defining some variables. But rewrite has own $1..$9 params.
How I may use $1 form if in the rewrite statement?

I think the regex dollar forms only apply to the most recent regular expression. So you cannot combine the $1 of the if with the $1 of the rewrite without using set. However, there are simpler solutions for your scenario.
Firstly, if you know the host name (for example example.com), you can do the following:
server {
server_name www.example.com;
return 301 $scheme://example.com$request_uri;
}
server { server_name example.com; ... }
On the other hand, if you don't have a specific host name in mind, you can do the following catch-all solution:
server {
server_name ~^www\.(?<domain>.+)$;
return 301 $scheme://$domain$request_uri;
}
server { server_name _; ... }
You can find out more about this second form here.
I don't recommend catch-all solutions because it is only meaningful to have at most one catch-all server block. If possible, use the named server solution.
Also, note that you can achieve the above redirection using the rewrite ^ destination permanent; form. All these solutions avoid using the poorly regarded if directive.

Related

What's the rewrite syntax for an NGINX regex location?

Using NGINX as a load balancer running on 10.1.2.15:9002, I have a need to rewrite http://10.1.2.15:9002/proxy.stream?opt=1 to http://10.1.2.15:9002/app/proxy.stream?opt=1.
Following are bits from my nginx.conf file:
http {
upstream app_cluster {
server 10.1.2.23:8080;
server 10.1.2.25:8080;
}
server {
listen 9002 default_server;
location /app/ {
proxy_pass http://app_cluster/;
}
location ~ ^/proxy.stream(.*)$ {
rewrite ^(.*)$ /app/$request_uri last;
}
}
}
By the way, I can replace the rewrite line with return 401 (for example), and I can see the 401 HTTP status returned using Chrome Developer Tools, so I know the regex is matching. I just can't get the URI rewritten properly. In fact, I only see the original request with a 406 status in Developer Tools, so I suspect something is wrong with my rewrite syntax.
Does anyone see what is wrong with this configuration?
Using $request_uri in the replacement string of a rewrite statement is problematic, as it has not been normalised and also contains the query string, which by default, rewrite will append again.
Also, your replacement string contains //, as you are appending a URI which already has a leading /.
The regular expression location is not necessary, as a prefix or exact match location will suffice and is more efficient for nginx to process. See this document for more.
For example:
location /proxy.stream {
rewrite ^ /app$uri last;
}
Make use of the matching part from the regex instead of $request_uri
rewrite ^(.*)$ /app/$1 last;

Chage the part of the URL using nginx

I m using nginx webserver.
I want to change the url before it hits the server from
https://www.example.com/abc/contact-us
to
https://www.example.com/#/contact-us
Thanks in advance.
For a single URI redirection, an exact match location and return statement may be most efficient:
location = /abc/contact-us {
return 301 /#/contact-us;
}
To redirect all URIs beginning with /abc use a rewrite directive:
location ^~ /abc/ {
rewrite ^/abc(.*)$ /#$1 permanent;
}
The location block is largely redundant, but means nginx only looks at the regular expression when it needs to. See this document for more.

Nginx - Redirect Domain Trailing Dot

How can I redirect "http://domain.com." to "http://domain.com" with Nginx?
What's the recommended way of doing this? Regex or is there any other options?
The following snippet does this in a general way, without having to hard code any hostnames (useful if your server config handles requests for multiple domains). Add this inside any server definition that you need to.
if ($http_host ~ "\.$" ){
rewrite ^(.*) $scheme://$host$1 permanent;
}
This takes advantage of the fact (pointed out by Igor Sysoev) that $host has the trailing dot removed, while $http_host doesn't; so we can match the dot in $http_host and automatically use $host for the redirect.
You will need to use Regex.
server {
listen 80;
server_name domain.com.WHATEVER, domain.com.WHATEVER-2, domain.com.WHATEVER-3;
rewrite ^ $scheme://domain.com$request_uri? permanent;
}
From: http://wiki.nginx.org/HttpRewriteModule
redirect - returns temporary redirect with code 302; it is used if the substituting line begins with http://
permanent - returns permanent redirect with code 301

Nginx rewrite syntax

I've seen a bunch of ngnix rewrites that have syntax like this:
server {
server_name www.example.com;
rewrite ^(.*) http://example.com$1 permanent;
}
I don't understand the ^(.*) part. Does the ^ take everything after the TLD of the uri?
The ^ does indeed match at the beginning of the string. In the case of nginx's rewrite directive this means the beginning of the path component of the actual URI. Unfortunately nginx's documentation is slightly incorrect. Quoting from http://www.nginx.org/en/docs/http/ngx_http_rewrite_module.html#rewrite :
If the specified regular expression matches a URI, the URI is changed as specified in the replacement string.
However, this is technically wrong. rewrite does not match the whole URI/URL but only its path component (which always starts with a / even if the user only enters e.g. http://www.example.com instead of http://www.example.com/). Therefore rewrite ^(.*) http://example.com$1 permanent; does not turn into http://example.comwww.example.com.
If I remember it correctly, the ^ just sets the Regex rule to match the start of the string.
The parentheses are used to extract that part with the $1-9 variables.
Another solution from the Nginx wiki. Link
server {
server_name www.example.com;
rewrite ^ http://example.com$request_uri? permanent;
}

Nginx rewrite non-www-prefixed domain to www-prefixed domain

I see the Nginx HttpRewriteModule documentation has an example to rewrite a www-prefixed domain to a non-www-prefixed domain:
if ($host ~* www\.(.*)) {
set $host_without_www $1;
rewrite ^(.*)$ http://$host_without_www$1 permanent; # $1 contains '/foo', not 'www.mydomain.com/foo'
}
How can I do the reverse-- rewrite a non-www-prefixed domain to a www-prefixed domain? I thought maybe I could do something like the following but Nginx doesn't like the nested if statement.
if ($host !~* ^www\.) { # check if host doesn't start with www.
if ($host ~* ([a-z0-9]+\.[a-z0-9]+)) { # check host is of the form xxx.xxx (i.e. no subdomain)
set $host_with_www www.$1;
rewrite ^(.*)$ http://$host_with_www$1 permanent;
}
}
Also I wanted this to work for any domain name without explicitly telling Nginx to rewrite domain1.com -> www.domain1.com, domain2.com -> www.domain2.com, etc. since I have a large number of domains to rewrite.
As noted in the Nginx documentation, you should avoid using the if directive in Nginx where possible, because as soon as you have an if in your configuration your server needs to evaluate every single request to decide whether to match that if or not.
A better solution would be multiple server directives.
server {
listen 80;
server_name website.com;
return 301 $scheme://www.website.com$request_uri;
}
server {
listen 80;
server_name www.website.com;
...
}
If you're trying to serve an SSL (HTTPS) enabled site, you got more or less three different options.
Set up multiple IP addresses having each server directive listening on their own IP (or different ports if that's an option for you). This options needs SSL certificates for both website.com and www.website.com, so either you have a wild card certificate, a UNI certificate (multiple domains) or just plainly two different certificates.
Do the rewrite in the application.
Use the dreaded if directive.
There is also an option to use SNI, but I'm not sure this is fully supported as of now.
if ($host !~* ^www\.) {
rewrite ^(.*)$ http://www.$host$1 permanent;
}
Well I guess I don't really need the outer "if" statement since I'm only checking for domains of the form xxx.xxx anyways. The following works for me, though it's not robust. Let me know if there is a better solution.
if ($host ~* ^([a-z0-9\-]+\.(com|net|org))$) {
set $host_with_www www.$1;
rewrite ^(.*)$ http://$host_with_www$1 permanent;
}
Edit: Added hyphen to the regular expression since it is a valid character in a hostname.
if ($host ~* ^[^.]+\.[^.]+$) {
rewrite ^(.*)$ https://www.$host$1 permanent;
}
It's only possible to get valid hostnames because the request will never make it to your server otherwise, so there's no need to build your own validation logic.
The nginx documentation cautions against the use of if for rewriting. Please see the link here: http://wiki.nginx.org/Pitfalls#Server_Name
HTTP & HTTPS without if conditions:
server {
listen 80;
listen 443;
server_name website.com;
return 301 $scheme://www.website.com$request_uri;
}
server {
listen 80;
listen 443 default_server ssl;
server_name www.website.com;
# Your config goes here #
}
Solution for multiple domains, working on nginx 1.17 for me:
server {
listen 80;
server_name .example.com;
set $host_with_www $host;
if ($host !~* www\.(.*)) {
set $host_with_www www.$host;
}
return 301 https://$host_with_www$request_uri;
}
In this config example additionally rewrites HTTP on HTTPS, if you don't want rewrite — replace https:// with http:// in return string.
If you want keep protocol — use $scheme variable.

Resources