I am using NGINX as a reverse proxy and have the following setup that redirects all requests to my SPA container
location / {
proxy_pass http://172.x.x.x;
proxy_intercept_errors on;
error_page 404 = /index.html;
}
This works but all images have their expiry set to the default (max-age=3600).
When I add the following block that is supposed to set the expiry for all image types then none of the images work anymore (404 NOT FOUND).
location ~* \.(jpg|png)$ {
add_header Cache-Control public;
expires 1M;
}
I tried nesting this block inside the first one but then I also get the 404 responses.
Why doesn't this work and what should I change so that all images have an extended max-age?
Nginx chooses one location to process a request. It uses the directives within the location block or inherited from the surrounding block.
If the requests for .jpg and .png URIs are to be passed upstream, the location also needs to contain a proxy_pass statement.
Alternatively, use a map to set the expires value. See this document for details.
For example:
map $request_uri $expires {
default off;
~*\.(jpg|png)(\?|$) 1M;
}
server {
...
expires $expires;
location / {
...
}
}
I think that expires already sets the cache-control header, but you can also use another map to set the value of an add_header directive.
Related
We have a couple of backends sitting behind our nginx front ends.
Is it possible to intercept 301 / 302 redirects sent by these backends and have nginx handle them?
We were thinging something alone the lines of:
error_page 302 = #target;
But I doubt 301/302 redirects can be handled the same as 404's etc etc... I mean, error_page probably doesnt apply to 200, etc error codes?
So to summarize:
Our backends send back 301/302s once in a while. We would like to have nginx intercept these, and rewrite them to another location block, where we could do any number of other things with them.
Possible?
Thanks!
You could use proxy_redirect directive:
http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_redirect
Nginx will still return 301/302 to the client but proxy_redirect will modify Location header and the client should make a new request to the URL given in the Location header.
Something like this should make the subsequent request back to nginx:
proxy_redirect http://upstream:port/ http://$http_host/;
I succeeded in solving a more generic case when a redirect location can be any external URL.
server {
...
location / {
proxy_pass http://backend;
# You may need to uncomment the following line if your redirects are relative, e.g. /foo/bar
#proxy_redirect / /;
proxy_intercept_errors on;
error_page 301 302 307 = #handle_redirects;
}
location #handle_redirects {
set $saved_redirect_location '$upstream_http_location';
proxy_pass $saved_redirect_location;
}
}
Alternative approach, which is closer to what you describe, is covered in ServerFault answer to this question: https://serverfault.com/questions/641070/nginx-302-redirect-resolve-internally
If you need to follow multiple redirects, modify Vlad's solution as follows:
1) Add
recursive_error_pages on;
to location /.
2) Add
proxy_intercept_errors on;
error_page 301 302 307 = #handle_redirect;
to the location #handle_redirects section.
More on proxy_redirect, for relative locations
Case
location /api/ {
proxy_pass http://${API_HOST}:${API_PORT}/;
}
the backend redirects to a relative location, which misses the /api/ prefix
the browser follows the redirection and hits a wall of incomprehension
Solution
location /api/ {
proxy_pass http://${API_HOST}:${API_PORT}/;
proxy_redirect ~^/(.*) http://$http_host/api/$1;
}
I've a NGINX Reverse Proxy setup for my NodeJS app.
My location block looks basically like this
location / {
proxy_pass http://localhost:3000;
gzip on;
gzip_static on;
expires 86400;
add_header Cache-Control "public";
}
No I want to expand this location for specific file types (js,css,png, ..) with
expires: max;
In case I couldn't overwrite the initial expires header I would be fine leaving it in the location block.
My static files are all located in /static/.. and I just could setup a second location block, but this would generate some overhead (setting the proxy_pass again etc). So I'm curious if I can expand blocks
You'll need a second location block as proxy_pass directives (all handlers, for that matter, see this post) aren't inherited by nested location blocks.
If you want to simplify your configuration without copy-and-pasting your other configuration, consider an include directive and put the common aspects of your location block into an external file.
It is technically possible that you could get away with just one location block by adding an if statement like:
if ($request_uri ~ ^/static) {
expires max;
}
but as per the Nginx wiki's If is Evil page, you'll want to test this and very carefully. This also involves more of an overhead by invoking the regex engine; the better option is two location blocks.
I am having trouble defining a location block for certain paths and file types.
I am using wordpress and using a plugin which generates dynamic sitemaps..It redirects to path like sitemapindex.xml, which do not actually exist and nginx is trying to serve it statically.
I need to be able to pass this to apache
I need to send anything that is http://example.com/blog/*.xml to apache. This is what i am trying, which does not work.. so for instance:
http://example.com/blog/post.xml or http://example.com/blog/sitemapindex.xml
nginx config
server {
location ~* ^/blog/*.xml$ {
include /etc/nginx/proxy_params;
proxy_pass http://127.0.0.1:8080;
}
}
what is the correct syntax
Thanks
I had similar problem with my images. In my applications, images were being served from two different locations.
You can specify different sources based on url pattern. Your solution would then look something like this.
location ~* ^/blog/.+\.(xml)$ {
root /some/path/;
expires 90d;
}
location ~* \.(xml|js|jpg|png|css|html|otf|eot|svg|ttf)$ {
root /some/other/path/;
expires 30d;
index index.html;
}
Gotta escape that period
server {
location ~* ^/blog/.*\.xml$ {
proxy_pass http://127.0.0.1:8080;
}
}
I have 3 different stockage area : "avatars" , "articles", "trends" where I store my images.
I want to "link" the URL "/trends/123.jpg" to trends folder , "/avatars/23.jpg" to avatar and so on.
Configuration 1:
server
{
listen 8089;
server_name localhost;
root /var/www;
location /trends/
{
alias /var/storage/hottrend/;
}
location ~* ^.+\.(jpeg|gif|png|jpg)
{
add_header Cache-control "public";
access_log off;
expires 90d;
}
}
Configuration 1 : "GET /trends/123.jpg" never match /trends/ location, why ?
Configuration 2:
server
{
listen 8089;
server_name localhost;
root /var/www;
location ~ ^/trends/(.*)\.jpg$
{
rewrite ^/trends/(.*)$ /$1 break;
root /var/storage/hottrend;
}
location ~* ^.+\.(jpeg|gif|png|jpg)
{
add_header Cache-control "public";
access_log off;
expires 90d;
}
}
Configuration 2: The last rule with the caching stuff is not matched. What is the best approach to server JPG files from different location/root ?
The two configurations have different but related problems. The two issues are:
the order in locations are matched; and
what happens when a location is matched.
I'll first explain how it works, and then I'll address your configurations.
How it works
Location matching
You can find the details on this nginx wiki page, but I have always found the wording to be confusing. (It mixes implementation details in the description of behaviour.) What it means is that locations are matched in the following order:
exact matches like location = /robots.txt
eager non-regex prefixes like location ^~ /trends/
regex matches like location ~* \.(jpg|png), or case-sensitive location ~ \.(jpg|png)
lazy non-regex prefixes like location /trends/ or location /
If multiple regular expressions match, then the first match beats the others. If multiple non-regex prefix match, I think it selects the most specific match -- I'll check this and update.
Location behaviour
A matching location is responsible for serving the designated content. It is also responsible for providing cache-control headers and so on. You can have a location that matches particular URL patterns to apply specific headers, but that location must also serve the content. If it cannot serve the content, you will most likely get an 404 error -- it won't look for other matching locations.
Lastly, be extra careful if you have a rewrite within a location. An internal redirect can happen earlier than some directives, in which case, those directive may not apply before the redirect causes the locations to be searched again.
Configuration 1
Your trends location is a lazy non-regex prefix, so it would only match if the regex location fails to match. You can fix this by using an eager non-regex match, such as
location ^~ /trends {
...
}
However, doing so will highlight the other configuration problem.
Configuration 2
You have two locations that could potentially match jpg files. Only one will succeed. If the first matches, then the cache control of the second location won't be applied. If the second matches, then the alias won't take effect.
The fix is to make sure that all directives needed are applied within the location that matches. You can either be explicit in one file, such as
location ^~ /trends
{
alias /var/storage/hottrend;
add_header Cache-control "public";
access_log off;
expires 90d;
}
location ~* ^.+\.(jpeg|gif|png|jpg)
{
add_header Cache-control "public";
access_log off;
expires 90d;
}
The neater solution for managing directives that must be applied to several locations is to factor those details into another file, and then include it at both locations. (Mohammad AbuShady did so in his example.) Something like this:
# Inside your main .conf
location ^~ /trends
{
alias /var/storage/hottrend;
include image-headers.conf;
}
location ~* ^.+\.(jpeg|gif|png|jpg)
{
include image-headers.conf;
}
# Inside image-headers.conf
add_header Cache-control "public";
access_log off;
expires 90d;
well I don't usually use alias but I think this is a good example to use alias for, you can put your caching settings inside /etc/nginx/image_caching.conf. If trends is only used for images i would not try to complicate the regex and assume that it always has images
location /trends {
alias /var/storage/trends;
include /etc/nginx/image_caching.conf;
}
And for your example config, you should have not added /trends inside root /var/storage/trends; because it's already used in the location, so i think it would show 404 becuase it's looking for /var/storage/trends/trends/123.jpg
Nginx checks locations defined as regular expressions in the order in which they appear in the configuration file and uses the location with the first matching expression.
Therefore, you should make sure location ~\.jpg$ appears before any other locations defined as regular expressions
I am looking for a nginx config setup that does setup the Access-Control-Allow-Origin to the value received in the Origin.
It seems that the * method doesn't work with Chrome and the multiple URLs doesn't work with Firefox as it is not allowed by CORS specification.
So far, the only solution is to setup the Access-Control-Allow-Origin to the value received in the origin (yes some validation could be implemented).
The question is how to do this in nginx, preferably without installing additional extensions.
set $allow_origin "https://example.com"
# instead I want to get the value from Origin request header
add_header 'Access-Control-Allow-Origin' $allow_origin;
Using if can sometimes break other config such as try_files. You can end up with unexpected 404s.
Use map instead
map $http_origin $cors_header {
default "";
"~^https?://[^/]+\.example\.com(:[0-9]+)?$" "$http_origin";
}
server {
...
location / {
add_header Access-Control-Allow-Origin $cors_header;
try_files $uri $uri/ /index.php;
}
...
}
If is evil
I'm starting to use this myself, and this is the line in my current Nginx configuration:
add_header 'Access-Control-Allow-Origin' "$http_origin";
This sets a header to allow the origin of the request as the only allowed origin. So where ever you are coming from is the only place allowed. So it shouldn't be much different than allowing "*" but it looks more specific from the browser's perspective.
Additionally you can use conditional logic in your Nginx config to specify a whitelist of hostnames to allow. Here's an example from https://gist.github.com/Ry4an/6195025
if ($http_origin ~* (whitelist\.address\.one|whitelist\.address\.two)$) {
add_header Access-Control-Allow-Origin "$http_origin";
}
I plan to try this technique in my own server to whitelist the allowed domains.
Here is a part of a file from conf.f directory where people always describes their virtual hosts of Nginx.
$http_origin compares with list of allowed_origins and then in second map block the system decides what will write to "header Access-Control-Allow-Origin" according to allowed list.
Here is a part of code.
#cat /etc/nginx/conf.d/somehost.conf
map $http_origin $origin_allowed {
default 0;
https://xxxx.yyyy.com 1;
https://zzz.yyyy.com 1;
}
map $origin_allowed $origin {
default "";
1 $http_origin;
}
server {
server_name somehost.com;
#...[skipped text]
add_header Access-Control-Allow-Origin $origin always;
#....[skipped text]
}
I test it om my servers. All works fine.
Have a nice day & be healthy,
Eugene.