Nginx redirect after slash in location - regex

I have domine name https://example.com/API/, I wanted to redirect anything given after /API/ for example :
https://example.com/API/test to https://example.com/API/
Below is my Nginx conf
location #error {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/var/run/php/php7.4-fpm.sock;
root /var/www/html/test/;
index index.html index.htm;
internal;
}
location ~*/api {
rewrite ^/api(.*) $1 break;
proxy_pass http://127.0.0.1:3100;
client_max_body_size 60M;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
error_page 502 #error;
}
That from the above example if /API/ gets 502 I am redirecting it to. PHP file is working fine, But if there is anything given after /API/test it is showing 404 not found.

You can have something of this sort:
server {
root /var/www/html; #your own values
server_name _; #website name
location #error {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/var/run/php/php7.4-fpm.sock;
root /var/www/html/test/;
index index.html index.htm;
internal;
}
location /api {
proxy_pass http://127.0.0.1:3100;
client_max_body_size 60M;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
error_page 502 #error;
}
}
So basically you would have to use location /api {} directive and that would work.

Related

nginx with Etherpad in a subdirectory

I am setting up etherpad-lite in a subdirectory at this location.
Unfortunately the files in 'static' aren't being loaded:
Clearly something is going on in my nginx, which (partially) looks like this:
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 80;
listen [::]:80;
server_name _
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl;
server_name www.whitewaterwriters.com;
ssl_certificate /etc/letsencrypt/live/www.whitewaterwriters.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/www.whitewaterwriters.com/privkey.pem;
return 301 https://whitewaterwriters.com$request_uri;
}
server {
listen 443 ssl;
server_name whitewaterwriters.com;
ssl_certificate /etc/letsencrypt/live/whitewaterwriters.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/whitewaterwriters.com/privkey.pem;
root /usr/share/nginx/html;
index index.html index.php;
# Load configuration files for the default server block.
include /etc/nginx/default.d/*.conf;
location ~ \.php$ {
fastcgi_pass unix:/var/run/php-fpm/php-fpm.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
location ~/watchtower/.*/live/pdfs/ {
autoindex on;
}
location /watchtower {
root /usr/share/nginx/html/;
}
location /etherpad {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_redirect off;
proxy_read_timeout 300;
proxy_pass http://localhost:9001/;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
}
location /{
root /usr/share/nginx/html/whitewaterwriters-site/_site/;
}
error_page 404 /404.html;
location = /404.html {
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
}
}
# Settings for a TLS enabled server.
#
# server {
# listen 443 ssl http2;
# listen [::]:443 ssl http2;
# server_name _;
# root /usr/share/nginx/html;
#
# ssl_certificate "/etc/pki/nginx/server.crt";
# ssl_certificate_key "/etc/pki/nginx/private/server.key";
# ssl_session_cache shared:SSL:1m;
# ssl_session_timeout 10m;
# ssl_ciphers PROFILE=SYSTEM;
# ssl_prefer_server_ciphers on;
#
# # Load configuration files for the default server block.
# include /etc/nginx/default.d/*.conf;
#
# error_page 404 /404.html;
# location = /40x.html {
# }
#
# error_page 500 502 503 504 /50x.html;
# location = /50x.html {
# }
# }
}
My question is: how do I configure nginx so that the missing files appear?
There are some other questions on this topic both in the github issues and SE, but they, in general, are solved by moving from etherpad to etherpad-lite, which I already use, or are both unanswered and approaching a decade old...
Short answer: if you add a trailing slash to your prefixed location, everything would work as expected.
map $http_upgrade $connection_upgrade {
'' close;
default upgrade;
}
server {
...
location /etherpad/ {
proxy_buffering off; # recommended by etherpad nginx hosting examples
proxy_set_header Host $host;
# optional headers
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr; # EP logs to show the actual remote IP
proxy_set_header X-Forwarded-Proto $scheme; # for EP to set secure cookie flag when https is used
# recommended with keepalive connections
proxy_http_version 1.1;
# WebSocket support
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
# upstream
proxy_pass http://127.0.0.1:9001/;
}
}
If you want /etherpad URI to work too, add the following location if you won't get HTTP 301 redirect from /etherpad to /etherpad/ with the above configuration:
location = /etherpad {
return 301 /etherpad/;
}
For me it wasn't necessary, but it can depend on your server environment.
To preserve query string, if any, you can use return 301 /etherpad/$is_args$args; or rewrite ^ /etherpad/ permanent instead.
Long answer (and what happened undercover).
There are many question on SO about "how can I host a webapp under an URI prefix". Here is one on my answers and here is a ServerFault thread on the similar topic.
The only right way to do it is to made your proxied app request its assets via relative URIs only (consider assets/script.js instead of /assets/script.js) or using the right URI prefix (/etherpad/assets/script.js).
Luckily, etherpad requests its assets using a relative paths (e.g. <script src="static/js/index.js"></script>) making it suitable to be hosted under any URI prefix. The problem is, when your origin URI is /etherpad, browser considers the current remote web server directory as the root one, and requests above script from server as scheme://domain/static/js/index.js. That request won't even caught by your location /etherpad { ... } (since it isn't starts with /etherpad). On the other hand, when your origin URI is /etherpad/, browser considers the current remote web server directory as the /etherpad/ and correctly requests above script from server as scheme://domain/etherpad/static/js/index.js.
Now let's see what happened with the proxied request /etherpad/<path> using your original configuration. Since you are using a trailing slash after the upstream address (http://localhost + /), nginx cut the location /etherpad prefix from the request URI and prepend it with that slash (or any other URI used in a proxy_pass directive after the upstream name) resulting in //<path>. You can read A little confused about trailing slash behavior in nginx or nginx and trailing slash with proxy pass SO threads for more details. Anyway that URI won't served by etherpad giving you Cannot GET //<path> error.
Changing location /etherpad { ... } to the location /etherpad/ { ... } you'll made both of the aforementioned problems gone.
A few words about the etherpad wiki examples, especially this one.
Both
location /etherpad/ {
proxy_pass http://127.0.0.1/;
...
}
and
location /etherpad/ {
rewrite ^/etherpad(/.*) $1 break;
proxy_pass http://127.0.0.1;
...
}
do the same string - stripping the /etherpad prefix from the request URI before passing it to the upstream. However the first one do it in a much more efficient way. It is a good practice to avoid regular expressions whenever possible. Using
location = /etherpad {
return 301 /etherpad/;
}
is also more efficient than
rewrite ^/etherpad$ /etherpad/ permanent;
Second and third location blocks from the above wiki example completely duplicate functionality from the first one. Moreover, that example breaks WebSocket support (whoever wrote it, he can at least add that support to the location /pad/socket.io { ... } block).
And never do the thing used at this example:
location ~ ^/$ { ... }
Use exact matching location instead:
location = / { ... }
Here is one more configuration I've tested in order to check if I can serve etherpad static assets directly via nginx. It seems to be workable, although I didn't tested it a lot. It uses an uncompressed js/css assets versions (which should not impact performance when you are using gzip or some other compression). It is also a good example of a configuration where you can't avoid using rewrite directive to strip a prefix from the request URI.
location /etherpad/static/ {
# trying to serve assets directly via nginx
# if the asset is not found, pass the request to the nodejs upstream
rewrite ^/etherpad(/.*) $1 break;
root /full/path/to/etherpad-lite/src;
try_files $uri #etherpad;
}
location /etherpad/ {
proxy_buffering off;
proxy_set_header Host $host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_http_version 1.1;
proxy_pass http://127.0.0.1:9001/;
}
location #etherpad {
proxy_redirect / /etherpad/;
proxy_buffering off;
proxy_set_header Host $host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_http_version 1.1;
proxy_pass http://127.0.0.1:9001;
}
Update
As being suggested on the GitHub, URIs started with /etherpad/static/plugins/ prefix should always be passed to the nodejs upstream since there are no corresponding assets would exists under the /path/to/etherpad/src/static/ directory. Despite there is already defined fallback to the nodejs upstream (try_files $uri #etherpad), to eliminate an extra stat system call produced by the try_files directive we can modify the above configuration to this one:
location ~ ^/etherpad/static/(?!plugins/) {
# trying to serve assets directly via nginx
# if the asset is not found, pass the request to the nodejs upstream
rewrite ^/etherpad(/.*) $1 break;
root /full/path/to/etherpad-lite/src;
try_files $uri #etherpad;
}
location /etherpad/ {
proxy_buffering off;
proxy_set_header Host $host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_http_version 1.1;
proxy_pass http://127.0.0.1:9001/;
}
location #etherpad {
proxy_redirect / /etherpad/;
proxy_buffering off;
proxy_set_header Host $host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_http_version 1.1;
proxy_pass http://127.0.0.1:9001;
}
(using negative lookahead regex, better readability) or to this one:
location /etherpad/ {
proxy_buffering off;
proxy_set_header Host $host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_http_version 1.1;
proxy_pass http://127.0.0.1:9001/;
}
location /etherpad/static/ {
rewrite ^/etherpad(/.*) $1 break;
root /full/path/to/etherpad-lite/src;
try_files $uri #etherpad;
}
location /etherpad/static/plugins/ {
proxy_buffering off;
proxy_set_header Host $host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_http_version 1.1;
proxy_pass http://127.0.0.1:9001/static/plugins/;
}
location #etherpad {
proxy_redirect / /etherpad/;
proxy_buffering off;
proxy_set_header Host $host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_http_version 1.1;
proxy_pass http://127.0.0.1:9001;
}
(only prefix locations, better performance). The repetitive part
proxy_buffering off;
proxy_set_header Host $host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_http_version 1.1;
and probably other optional headers (X-Real-IP, X-Forwarded-For, X-Forwarded-Proto) setup mentioned at the very beginning of the answer, can be used as a separate file, e.g. etherpad-proxy.conf, and included into the main nginx config with the include directive.
You can try to navigate the static content to the correct folder with:
location /static {
root root /usr/share/nginx/html/whitewaterwriters-site/_site/static;
}
# or something like:
location /etherpad/static {
root root /usr/share/nginx/html/whitewaterwriters-site/_site/;
}
since this is working: https://whitewaterwriters.com/etherpad/static/js/vendors/html10n.js?v=869d568c

Nginx append a trailing slash to the url

I am having an issue in Safari and IE where a django redirect does not include the authorization headers. This leads to the requests being rejected.
These redirects happen with the URL does not end with a /.
So I am trying to handle adding the / in the nginx config before it is passed to the app.
Here is my nginx config:
server {
server_name ~^((stage|prod)-)?chalktalk-react-40.*;
listen 28000 default_server;
location ~ ^/static/(?P<file>.*) {
root /chalktalk/var/chalktalk-react-40;
add_header 'Access-Control-Allow-Origin' $cors_origin;
# Inform downstream caches to take certain headers into account when reading/writing to cache.
add_header 'Vary' 'Accept-Encoding,Origin';
try_files /staticfiles/$file =404;
}
location ~ ^/media/(?P<file>.*) {
root /chalktalk/var/chalktalk-react-40;
try_files /media/$file =404;
}
location / {
if ($request_uri ~ ^([^.\?]*[^/])$) {
return 301 $1/;
}
}
# API endpoints have their own authentication and authorization
# schemes, so we bypass basic auth.
location ~ ^/(api|admin|ws)/ {
try_files $uri #proxy_to_app;
}
rewrite ^(.*)/favicon.ico$ /static/pages/homepage/logo-nonname.png last;
location #proxy_to_app {
proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto;
proxy_set_header X-Forwarded-Port $http_x_forwarded_port;
proxy_set_header X-Forwarded-For $http_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_read_timeout 1200;
proxy_redirect off;
proxy_pass http://chalktalk-react-40_app_server;
}
# Forward to HTTPS if we're an HTTP request...
if ($http_x_forwarded_proto = "http") {
set $do_redirect "true";
}
# Run our actual redirect...
if ($do_redirect = "true") {
rewrite ^ https://$host$request_uri? permanent;
}
}
I know I have to add a rewrite and this is the rewrite I am trying to add:
server {
...
listen 28000 default_server;
rewrite ^([^.]*[^/])$ $1/ permanent;
...
}
The issue that I have with that is when I try to visit: example.com/admin, I get example.com:28000/admin/.
How do i make sure it leads to this example.com/admin/ without the port # inserted there?
UPDATE:
I updated the config to have the following:
server {
server_name ~^((stage|prod)-)?chalktalk-react-40.*;
listen 28000 default_server;
port_in_redirect off;
rewrite ^([^.]*[^/])$ $1/ permanent;
...
}
But I get this error in Safari:
Use either the port_in_redirect directive, for example:
port_in_redirect off;
Or tell Nginx exactly what you want in the rewrite directive, for example:
rewrite ^([^.]*[^/])$ $scheme://$host$1/ permanent;

Nginx Base Auth

I work with Django and Nginx
I added the following entry to my config to restrict access to example.com/admin/
The function asks for a password, and everything works, but after that, as I get a 404 Not Found error from Nginx
Full config
upstream rates_core_server {
server unix:/webapps/example.com_app/example.com/run/gunicorn.sock fail_timeout=0;
}
server {
listen 80;
server_name example.com www.example.com;
client_max_body_size 4G;
access_log /webapps/example.com_app/logs/nginx-access.log;
error_log /webapps/example.com_app/logs/nginx-error.log;
location /admin/ {
auth_basic "Restricted Content";
auth_basic_user_file /etc/nginx/.htpasswd;
}
location /static/ {
alias /webapps/example.com_app/example.com/static/;
client_max_body_size 100M;
}
location /media/ {
alias /webapps/example.com_app/example.com/static/media/;
client_max_body_size 100M;
}
location / {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_redirect off;
if (!-f $request_filename) {
proxy_pass http://example.com_server;
break;
}
}
# Error pages
error_page 500 502 503 504 /500.html;
location = /500.html {
root /webapps/example.com_app/example.com/static/;
}
}
I do not understand what the problem is
With the current config, nginx does not know where to look for or redirect to for admin block. Can you include proxy_pass settings in your admin block as well, like this:
location /admin/ {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_redirect off;
if (!-f $request_filename) {
proxy_pass http://example.com_server;
break;
}
auth_basic "Restricted Content";
auth_basic_user_file /etc/nginx/.htpasswd;
}

taking a django site down for maintenance?

I am running django with nginx/gunicorn. I am not deeply familiar with how nginx and gunicorn work, but suppose that I want to take my django site down for maintenance.
I assume I would be wanting to redirect to some simple maintenance page by going into the nginx/gunicorn settings and redirecting something but I could be wrong.
What is the correct (easiest) way to do this?
EDIT adding nginx config proxy statements:
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_redirect off;
proxy_pass http://app_server;
EDIT 2: adding nginx sites-enabled file
upstream app_server {
server 127.0.0.1:9000 fail_timeout=0;
}
server {
listen 80 default_server;
listen [::]:80 default_server ipv6only=on;
root /usr/share/nginx/html;
index index.html index.htm;
client_max_body_size 4G;
server_name _;
keepalive_timeout 5;
# Your Django project's media files - amend as required
location /media {
alias /home/django/mysite/media;
}
# your Django project's static files - amend as required
location /static {
alias /home/django/mysite/static_dump;
}
location / {
#proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
#proxy_set_header Host $http_host;
#proxy_redirect off;
#proxy_pass http://app_server;
if (-f /usr/share/nginx/html/index.html) {
return 503;
}
}
error_page 503 #maintenance;
location #maintenance {
rewrite ^(.*)$ /503.html break;
}
}
One of the ways to do is to add the maintenance page somewhere in the server, and then in your nginx file ( In the sites-enabled folder) redirect requests to the site to that maintenance page.
Your nginx page should contain:
server_name myhost.example.com;
root /path/to/html/file/directory;
index index.html;
Only the above 3 lines are enough

nginx + uwsgi custom 502 will not work

Hi I am trying to get a custom 502 page working on a website and can't seem to get it working.
Basically the way i'm testing it is I'm just stopping uwsgi and accessing the page and every time i get the default nginx 502 page. Can someone please explain to me how to get this working? I've been at this for over a week with 0 success. I have a file named 502.html in public_html and i can access it directly with http://ask.ploy.io/502.html but as soon as i stop uwsgi and try to access the main domain http://ask.ploy.io I get the default 502 page. Here is the vhost config:
### nginx vhost conf for ployio
server {
listen 80;
server_name ask.ploy.io www.ask.ploy.io;
access_log /usr/local/apache/domlogs/ask.ploy.io main;
error_log /home/ployio/access-logs/ask.ploy.io debug;
root /home/ployio/public_html;
index index.html index.htm index.php;
location /502.html {
root /home/ployio/public_html;
}
location ~ /\.ht {
deny all;
}
location / {
error_page 404 403 = #uwsgi;
log_not_found off;
error_page 502 /502.html;
root /home/ployio/public_html;
}
location #uwsgi {
internal;
uwsgi_pass unix:/home/ployio/.uwsgi/uwsgi.sock;
include /usr/local/nginx/conf/uwsgi_params;
}
location ~* ^.*\.php$ {
if (!-f $request_filename) {
return 404;
}
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://204.61.223.114:8888;
}
location /cpanel {
rewrite ^/(.*) https://cpanel.ask.ploy.io:2083/$1 permanent;
}
}
If 502 it's the only error code you want to handle with a custom error page, you just need to be specific in the location:
location /502.html {
root /home/ployio/public_html;
}
Your current location is only being matched with the exact "/50x.html" path, which indeed does not exists in your server: http://ask.ploy.io/50x.html
It's also possible using nginx variables ($uri or something similar) to redirect all 50x errors to that root directory, but for your needs this should be enough.
The main issue is in the location #uwsgi section.. it never seems to handle the 502 return correctly.. maybe by design?
This is a working config
server {
listen 80;
server_name ask.ploy.io www.ask.ploy.io;
access_log /usr/local/apache/domlogs/ask.ploy.io main;
error_log /home/ployio/access-logs/ask.ploy.io debug;
root /home/ployio/public_html;
index index.html index.htm index.php;
location / {
uwsgi_pass unix:/home/ployio/.uwsgi/uwsgi.sock;
include /usr/local/nginx/conf/uwsgi_params;
}
error_page 502 503 504 #maintenance;
location #maintenance {
root /home/ployio/public_html_502;
rewrite ^(.*)$ /502.html break;
}
}
Make sure you put the 502.html in a new root and reference it there.. ps.. randall sucks