NGINX: Serve static files from different locations - django

I have 2 servers implemented on two different machines (different IP adresses). Lets call them serverA and serverB.
serverA is where serverB is going to feed for some static files.
serverA configuration file is:
limit_req_zone $binary_remote_addr zone=lmz_serverA:10m rate=5r/s;
server {
listen 80; ## listen for ipv4; this line is default and implied
server_name serverA;
location /server_a {
limit_req zone=lmz_serverA burst=5 nodelay;
rewrite /server_a/(.*) /$1 break;
proxy_intercept_errors on;
proxy_pass http://0.0.0.0:8080;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location /static/BIG/ {
root /safe/server_a/;
autoindex off;
expires 7d;
}
location /server_a/static/{
root /safe/;
autoindex off;
expires 7d;
}
location = /favicon.ico{
alias /safe/server_a/static/images/favicon.ico;
}
}
serverB configuration file is:
limit_req_zone $binary_remote_addr zone=lmz_serverB:10m rate=5r/s;
server {
listen 80; ## listen for ipv4; this line is default and implied
server_name serverB;
location /server_b {
limit_req zone=lmz_serverB burst=5 nodelay;
rewrite /server_b/(.*) /$1 break;
proxy_intercept_errors on;
proxy_pass http://1.0.0.0:8080;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location /server_b/static/ {
root /safe/;
autoindex off;
expires 7d;
}
--- end
Now, imagine that this servers are in different continents.. some static files are OK to be served from serverA but the BIG (/static/BIG/) stuff is giving me some trouble because the majority of users are in the same continent of serverB. So I want to cut those BIG static files from serverA and put them on serverB so they can be more easily downloaded.
Does anyone have any idea how can I accomplish that just by making those files available on serverB and changing nginx configuration files?
IMPORTANT: serverA implements a Django application named appA, and serverB implements a different (yet still Django) application named appB. I can't change the code of those two apps.
Thanks in advance!

I stumbled onto this while searching for something similar -- how to serve a different filename than what's requested. My final solution:
location /robots.txt {
# Try the beta file, which has a disallow everything
root /location/to/static/files;
try_files /robots-beta.txt =404;
}

You can do a 302 Moved Temporarily redirect from the server which wants another one to serve the big files.
server_name serverA;
location /static/BIG/ {
return 302 $scheme://serverB$uri;
}

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

How to config nginx proxy pass using subfolder domain whith gunicorn Django

How can I configure ngnix to redirect a proxypass from a domain with subfolder to /?
Example:
https://example.com/yoursub/
to
localhost without /yoursub/ prefix
At the moment the direct access to the server ip http://xxx.xxx.xxx.xx/ from the intranet works without problems.
my nginx config file:
upstream app_server {
server unix:/home/myname/APP-Server/gunicorn/gunicorn.sock fail_timeout=0;
}
server {
listen 80;
# add here the ip address of your server
# or a domain pointing to that ip (like example.com or www.example.com)
server_name 123.456.789.1;
keepalive_timeout 5;
client_max_body_size 4G;
access_log /home/myname/APP-Server/logs/nginx-access.log;
error_log /home/myname/APP-Server/logs/nginx-error.log;
location /static/ {
alias /home/myname/APP-Server/static-root/;
}
# checks for static file, if not found proxy to app
location / {
try_files $uri #proxy_to_app;
}
location #proxy_to_app {
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 it's relevant: The backend is a django app with a gunicorn server. Do I have to consider anything about the redirect from https to http? I have no control over the base domain.
If I understand correctly, you want to remove the first part of the URI. There are multiple ways you can do that, but the easiest is probably with the alias directive, which will remove the portion of the URI that matches the current location block:
location /foo/ {
alias /home/myname/APP-Server/static-root/; # It doesn't really matter what you put here, since you're proxying everything.
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 your Nginx server is running on foobar.example and you request http://foobar.example/foo/bar, the upstream server will see a request for http://foobar.example/bar.
The alias directive can be a bit buggy/unintuitive, so it's best to keep your location directive top-level (not nested within other location blocks) and as simple as possible.
If instead you want to add a prefix to the URI, you can do that within the proxy_pass directive itself:
location #proxy_to_app {
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/foo$uri$is_args$args;
}
If your Nginx server is running on foobar.example and you request http://foobar.example/bar, the upstream server will see a request for http://foobar.example/foo/bar
Try this:
server {
...
location #proxy_to_app {
...
proxy_pass http://app_server/; # note the trailing slash
}
}
Explanation
As per the nginx docs:
If the proxy_pass directive is specified with a URI, then when a request is passed to the server, the part of a normalized request URI matching the location is replaced by a URI specified in the directive.
Since / location matches anything, then everything will be replaced by / (the trailing slash in proxy_pass) before being proxied to the upstream.
The way I handle this is with a rewrite rule:
location /yoursub {
rewrite /yoursub(.*) $1;
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;
}
The rewrite will convert your url from /yoursub/path/to/view to /path/to/view for your app server.

Excluding URLs from Django redirects

Im redirecting all my files on the root of my site to the English subdirectory
url(r'^(?P<zero>([a-z-]*)).(htm|php|html)$', redirect, {'to': '/en/{0}.html', 'permanent': True}),
I want to exclude a specific page. If I were using apache I could easily do this with an alias or a mod rewrite before it hits the Django application. However Im using Nginx and the redirect gets process regardless of the alias. Is there a rule I can use above so a specific page on my root won't get redirected?
the following is an example of my config with using regx, tried it with suggested one above as well (location ^~ /file.html { )
listen 8007;
server_name devel.somedomain.com;
access_log /var/log/nginx/somedomain-dev.log;
error_log /var/log/nginx/error.log;
real_ip_header X-Forwarded-For;
set_real_ip_from 0.0.0.0/0;
#real_ip_recursive on;
location ~ /help.cgi$ {
proxy_pass http://localhost:8006;
}
location ^~* /file\.html$ {
alias /home/admin/somedomain/file.html;
}
location / {
proxy_pass http://127.0.0.1:8006;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
It's even easier in nginx.
location ^~ /file_name {
alias /path/to/file_name;
}
Put that before the proxy_pass.

NGINX not serving htm files, but serving all other static files

I've been banging away at this for a bit and I'm obviously missing something simple. I have a Django application I'm serving on nginx. All the other static files across the application are being served ok, except for some .htm files that are part of the library for the TinyMCE HTML editor.
The path for the file not being served is:
http://www.myurl.org/static/admin/tinymce/jscripts/tiny_mce/plugins/advlink/link.htm/
The nginx log file states the error is:
"/home/deployer/cmp/cml/static/admin/tinymce/jscripts/tiny_mce/plugins/advlink/link.htm/index.html" is not found (20: Not a directory)"
(Incidentally, I don't know why nginx keeps thinking that file path leads to a directory.)
But, this test file:
http://www.myurl.org/static/admin/tinymce/jscripts/tiny_mce/plugins/advlink/tst.html
is served ok.
My config file is:
upstream app_server_wsgiapp {
server localhost:8000 fail_timeout=0;
}
server {
listen 80;
server_name XX.XXX.X.XX;
access_log /var/log/nginx/XX.XXX.X.XX.access.log;
error_log /var/log/nginx/XX.XXX.X.XX.error.log info;
keepalive_timeout 5;
#nginx serve up static files and never send to the WSGI server
location /static {
include /etc/nginx/mime.types;
autoindex on;
alias /home/deployer/cmp/cml/static;
}
location / {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_redirect off;
if (!-f $request_filename) {
proxy_pass http://app_server_wsgiapp;
break;
}
}
#this section allows Nginx to reverse proxy for websockets
location /socket.io {
proxy_pass http://app_server_wsgiapp/socket.io;
proxy_redirect off;
proxy_buffering off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
}
}
Relevant portions of mime types file:
types {
text/html html htm shtml;
text/css css;
text/xml xml rss;
image/gif gif;
image/jpeg jpeg jpg;
application/x-javascript js;
application/atom+xml atom;
...
}
Any suggestions to get that link.htm to be served?
Update: After trying a few things, I noticed that the nginx error log says: "/home/deployer/cmp/cml/static_proxy/index.html is not found" and the request is coming from: "GET /static_proxy/?u=myurl.org/static/admin/tinymce/jscripts/tiny_mce/plugins/advlink/link.htm
(For the record, this isn't my application, I'm helping a teacher friend migrate off Gondor to Linode after her Dev bailed.)
The static_proxy is coming from the tinymce lib. But, that isn't a problem in dev on my machine. Everything works great there. I tried adding in this location block, but that was just a flailing guess.
location /static_proxy {
autoindex on;
root /home/deployer/cmp/cml;
}
If I change the root directive in the above to
alias /home/deployer/cmp/cml/static;
I get the static directory listing in the pop-up, which seems like an improvement.
I had quite the same issue with Wordpress-Mailpoet-TinyMCE.
After struggling a bit, I noticed all the files ending with .htm or .html were not found. When renaming the same file with a different suffix like .jpg or .gif, it get served successfully.
So I finally add a special NGINX directive allowing htm and html files coming from the mailpoet extension.
Here it is :
# Mailpoet - tinyMCE quick fix
location ~ /wp-content/plugins/wysija-newsletters/js/tinymce/.*\.(htm|html)$ {
add_header Access-Control-Allow-Origin *;
access_log off; log_not_found off; expires 30d;
}
Hope it might help others...
I had an issue getting timymce to work with nginx.
The following link helped me solve the issue. Not sure if it's the best solution but it worked well for what I needed it to do.
https://techvangelist.net/nginx-global-rewrite-issue-js-css-images-not-found/
I was able to get tinymce to work by adding a directive like this...
#tinymce fix
location ~ .(mezzanine|tinymce)/(.+)$ {
try_files $uri $uri/ /$1/$2;
}

Nginx reverse proxy configuration for subdomain with multiple paths

I have a situation here with my Nginx reverse proxy configuration. My distribution is Ubuntu 14.04
I have a domain, let's call it foo.bar.net, and I want the /grafana endpoint to redirect to my grafana server (localhost:3000), the /sentry endpoint to redirect to my sentry server (localhost:9000) and finally, the /private endpoint to redirect to my django server (localhost:8001). I am using gunicorn for the tuneling between django and nginx.
Here is what I tried :
server {
# listen on port 80
listen 80 default_server;
# for requests to these domains
server_name foo.bar.net;
location /sentry {
# keep logs in these files
access_log /var/log/nginx/sentry.access.log;
error_log /var/log/nginx/sentry.error.log;
# You need this to allow users to upload large files
# See http://wiki.nginx.org/HttpCoreModule#client_max_body_size
# I'm not sure where it goes, so I put it in twice. It works.
client_max_body_size 0;
proxy_pass http://localhost:9000;
proxy_redirect off;
proxy_read_timeout 5m;
allow 0.0.0.0;
# make sure these HTTP headers are set properly
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location /grafana {
proxy_pass http://localhost:3000;
proxy_redirect off;
proxy_read_timeout 5m;
allow 0.0.0.0;
# make sure these HTTP headers are set properly
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location /private {
proxy_pass http://127.0.0.1:8001;
}
location /private/static/ {
autoindex on;
alias /home/user/folder/private/static/;
}
}
The server won't even start correctly, the config is not loading.
I would also like the / path to redirect to the private endpoint if possible.
Additionally, I am not even sure where to put this configuration (sites-available/??)
Can anyone help me with that ?
Thanks a lot,
There are some missing semicolons and other syntax errors. Look at main nginx error log for details and fix them one by one.
Where to put that config file depends on your distribution. For some of them it should be sites-available directory and symlink to that file inside sites-enabled directory for quick enabling and disabling sites, if you don't have sites-available and sites enabled directory, you should put it into conf.d dir in your distribution.