I enabled Brotli compression in Nginx for a dynamically generated, but rarely changing resource.
My expectation was that when Nginx caches upstream responses, it will also cache the compression result. Thus, I assumed the CPU cost of enabling Brotli would be negligible. Instead, I see a performance impact, confirmed by perf top to be related to Brotli.
I verified that caching to the upstream server works. However, Nginx stores in its cache only the uncompressed upstream requests. Because of that, it will have to run the costly Brotli compression for each request. That is the problem.
There are sources (relating to gzip compression) recommending to compress either in upstream, or if that is not an option to create a second Nginx to proxy the request through, which takes the role of upstream and does the compression. Both solutions are not very elegant.
Is there a way to make Nginx cache not only the uncompressed upstream requests, but the result of the compression as well?
Maybe I am overlooking some. Here is a simplified config:
proxy_cache_path /var/cache/nginx levels=1 keys_zone=my_config_cache:8M
inactive=60m use_temp_path=off;
server {
location = /foo {
proxy_pass http://test-upstream;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_ignore_headers Expires;
proxy_ignore_headers Cache-Control;
brotli on;
brotli_comp_level 11;
proxy_cache my_config_cache;
proxy_cache_valid 10s;
proxy_cache_use_stale error timeout http_500 http_502 http_503 http_504;
expires 60s;
}
}
brotli_comp_level 11;
It is too high. The recommendation is 4 for dynamic content.
You cannot do what you want with the current setup.
If you can just set up your upstream to be brotli-capable instead, then you can cache compressed responses from it, by putting $http_accept_encoding as part of the cache key. That alone, however, will not be good enough, because you will have to normalize its value (think, all possible Accept-Encoding incoming headers, will result in a bloated and highly inefficient cache).
If you really care about Brotli enabled clients (now that most of the browsers supporting Brotli anyway), and highest compression level possible, then you can
enforce compression to the brotli-capable upstream by supplying Accept-Encoding: br while proxy-passing, which would result in caches always having brotli encoded response. (you don't need to adjust your cache key then). However, this requires a feature, currently unavailable, e.g. I call it unbrotli.
The idea is that everything goes to upstream saying "I want Brotli encoded response". The upstream delivers Brotli-ed response (where applicable, of course, e.g. for text responses). But for clients that only support gzip or no compression at all, things should be dynamically uncompressed from Brotli (very low CPU impact). This isn't so great, but there is a declining number of Brotli incapable clients.
Related
I have a PHP CMS as an upstream/origin server that serves content. It is placed behind a Nginx web server configured as a reverse proxy cache with proxy_pass and proxy_cache.
While implementing 304 Not Modified response on the upstream/origin server, I noticed that I sometimes get 304 Not Modified response from the proxy without my upstream/origin server being called.
My proxy configuration looks like this (notice that I don't cache 304 responses):
http {
#...
proxy_cache_path /usr/share/nginx/cache/ levels=1:2 keys_zone=microcache:10m max_size=1024m inactive=1h;
}
server {
server_name _;
#...
location / {
proxy_cache microcache;
proxy_cache_revalidate on;
add_header X-Cache-Status $upstream_cache_status;
#...
proxy_cache_valid 200 301 302 1m;
proxy_pass http://upstream;
}
}
When hitting a URL for the first time, I get a 200 with a X-Cache-Status: MISS and the ETag and Last-Modified headers returned by the origin.
The second time I hit the URL, I get a X-Cache-Status: HIT and my origin doesn't get called, so all is great!
However, if I hit the URL again, this time with the If-Modified-Since and If-None-Match headers with the values received with the first call, I get a 304 Not Modified, with X-Cache-Status: HIT and still no call on my origin server!
I seems then that Nginx has the content in its cache, and figures with the incoming headers that it should return a 304. Meaning my origin never gets to return the 304 itself if the content is in the Nginx cache.
Am I right? I cannot find any documentation on this behavior. I, however found a module in the Nginx source code that seems the be handling something similar: https://github.com/nginx/nginx/blob/master/src/http/modules/ngx_http_not_modified_filter_module.c#L78
Could you please provide me with insights on this behavior, how it works, and what enables it in my configuration?
Thank you very much!
proxy_cache_path /usr/share/nginx/cache/ levels=1:2 keys_zone=microcache:10m max_size=1024m inactive=1h;
proxy_cache microcache;
Meaning that nginx server stores cache data to /usr/share/nginx/cache/
and
proxy_cache_valid 200 301 302 1m;
This means any HTTP API calls that have HTTP statuses 200, 301, or 302 use nginx local cache (which is stored in /usr/shre/nginx/cache) for one minute.
I want to set up an NGINX server which provides the following functionality:
When a request is made NGINX to get the page at /path/to/page, it fetches the page at /path/to/page.
If the upstream server is down or NGINX can't connect to it for some reason, NGINX returns a cached version of the page if it has one.
If the cached file is over 6 hours old, don't use it, just return a 502.
If the upstream server is available, never use the cache.
I have an NGINX config here which I think should work based on my understanding of the docs, but it doesn't and I can't see why. The problem is with point (4), this NGINX server returns the cached version of the file even if the upstream server is online.
daemon off;
error_log /dev/stdout info;
events {
}
http {
proxy_cache_path
"/home/jack/Code/NGINX Caching/Codebase/cache" # Cache path
keys_zone=cache:10m # Name of cacahe, max size for keys 10 megabytes
levels=1:2 # Don't store all cached files in a single directory
max_size=500m # Max size of cache
inactive=6h; # Cached file deleted if not used within six hours
proxy_cache_valid 6h;
proxy_cache_key "$request_method$request_uri";
access_log /dev/stdout;
server {
listen 8080;
location ~ ^/(.+)$ {
proxy_pass http://0.0.0.0:8000/$1;
proxy_cache cache;
proxy_cache_valid 6h;
proxy_buffering on;
proxy_cache_use_stale error timeout;
}
}
}
Replace the proxy_cache_path with a path to a directory on your machine, and run another webserver on your machine on port 8000. When I modify a file served by the server on port 8000, NGINX doesn't see the change until I erase the cache. The issue is with NGINX and not my client (Firefox), even if I turn off caching in the browser, NGINX returns a 200 with the old file contents.
Can you please check if these two directives might help you:
proxy_cache_revalidate:
http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_cache_revalidate
and
proxy_cache_use_stale: http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_cache_use_stale
There is a video from nginx.conf '17 online describing all the cool things you could achive with caching: https://www.youtube.com/watch?v=xZrOjmAkFC8. maybe this is also of interest for you.
So, it seems I misunderstood the NGINX proxy cache directives. The docs are quite confusing on this subject, so I'll lay it out point by point.
This official help page gives a decent overview of the various directives, however it makes no mention of something which as it turns out is a very important conceptual building block in understanding how NGINX caching works: the notion of a cached file being stale.
NGINX's default behavior is to always use the cache if it's there, rather than querying the upstream server. With this config, a minimal config to do caching, NGINX will query the upstream server the first time a page is accessed, and then use the cached version forever after that:
events {
}
http {
proxy_cache_path
/path/to/cache
keys_zone=my_cache:10m;
proxy_cache_key "$request_method$request_uri";
server {
listen 8080;
location ~ ^/(.+)$ {
proxy_pass http://0.0.0.0:8000/$1;
proxy_cache cache;
}
}
}
You can use the proxy_cache_valid directive to tell NGINX when a cached file should be considered "stale". For example, if we set proxy_cache_valid 5m, then 5 minutes after a cache file is created NGINX will stop serving it and querying the upstream server again on the next request. If the upstream is down, NGINX will return a 502. However, during those five minutes, NGINX will still use the cache even if the upstream server is available, so this is still not what we want.
NGINX has another directive, proxy_cache_use_stale, gives NGINX conditions under which it may use cached files even if they're stale. We can combine these together to get a server which caches pages, makes them stale immediately (or almost immediately), and then only uses them if the upstream is down:
events {
}
http {
proxy_cache_path
/path/to/cache
keys_zone=my_cache:10m;
proxy_cache_key "$request_method$request_uri";
server {
listen 8080;
location ~ ^/(.+)$ {
proxy_pass http://0.0.0.0:8000/$1;
proxy_cache cache;
proxy_cache_valid 1s;
proxy_cache_use_stale error timeout;
}
}
}
This config has almost the behavior we want, except that if the upstream server goes down for an extended period of time, NGINX will continue to use the cache indefinitely. As far as I know there is no way to tell NGINX to totally invalidate/clear a cached file after a given amount of time. Normally that's what proxy_cache_valid is for, but we're already using that for a different purpose, to make files stale after 1 second so they're only used when the upstream is down. We would need some next level after "stale" that means the file is completely invalidated, but I don't think that exists in NGINX.
So the simplest solution is to just clear the cache manually. It's sufficient to just delete all files in the cache directory (or its subdirectories) which were last modified more than 6 hours ago, or whatever you want the expiry time to be. On a Linux system, you can run this script every 5 minutes, for example:
find /path/to/cache -type f -mmin +360 -delete
To bypass cache if upstream is up (max-age 1) and use cache if down (proxy_cache_use_stale) I created following config:
proxy_cache_path /app/cache/ui levels=1:2 keys_zone=ui:10m max_size=1g inactive=30d;
server {
...
location /app/ui/config.json {
proxy_cache ui;
proxy_cache_valid 1d;
proxy_ignore_headers Expires;
proxy_hide_header Expires;
proxy_hide_header Cache-Control;
add_header Cache-Control "max-age=1, public";
proxy_cache_use_stale error timeout http_500 http_502 http_503 http_504;
add_header X-Cache-Status $upstream_cache_status;
add_header X-Cache-Date $upstream_http_date;
proxy_pass http://app/config.json;
}
}
But cache is not used when upstream is down and client only gets 504 Gateway Timeout. I've already read following articles:
https://nginx.org/ru/docs/http/ngx_http_proxy_module.html#proxy_cache_use_stale
How to configure NginX to serve Cached Content only when Backend is down (5xx Resp. Codes)?
https://serverfault.com/questions/752838/nginx-use-proxy-cache-if-backend-is-down
And It does not work as I expect. Any help is appreciated.
Let's discuss a really simple setup with two servers. One running apache2 serving a simple html page. The other running nginx that reverse proxies to the first one.
http {
[...]
proxy_cache_path /var/lib/nginx/tmp/proxy levels=2:2 keys_zone=one:10m inactive=48h max_size=16g use_temp_path=off;
upstream backend {
server foo.com;
}
server {
[...]
location / {
proxy_cache one;
proxy_cache_valid 200 1s;
proxy_cache_lock on;
proxy_connect_timeout 1s;
proxy_cache_use_stale error timeout updating http_502 http_503 http_504;
proxy_pass http://backend/
}
}
}
This setup works for me. The most important difference is the proxy_cache_valid 200 1s; It means that only responses with http code 200 will be cached, and will only be valid for 1 second. Which does imply that the first request to a certain resource will be get from the backend and put in the cache. Any further request to that same resource will be served from the cache for a full second. After that the first request will go to the backend again, etc, etc.
The proxy_cache_use_stale is the important part in your scenario. It basically says in which cases it should still serve the cached version although the time specified by proxy_cache_valid has already passed. So here you have to decided in which cases you still want to serve from cache.
The directive's parameters are the same as for proxy_next_upstream.
You will need these:
error: In case the server is still up, but not responding, or is not responding correctly.
timeout: connecting to the server, requesting or response times out. This is also why you want to set proxy_connect_timeout to something low. The default is 60s and is way to long for an end-user.
updating: there is already a request for new content on it's way. (not really needed but better from a performance point of view.)
The http_xxx parameters are not going to do much for you, when that backend server is down you will never get a response with any of these codes anyway.
In my real life case however the backend server is also nginx which proxies to different ports on the localhost. So when nginx is running fine, but any of those backends is down the parameters http_502, http_503 and http_504 are quit useful, as these are exactly the http codes I will receive.
The http_403, http_404 and http_500 I would not want to serve from cache. When a file is forbidden (403) or no longer on the backend (404) or when a script goes wrong (500) there is a reason for that. But that is my take on it.
This, like the other similar questions linked to, are examples of the XY Problem.
A users wants to do X, wrongly believes the solution is Y but cannot do Y and so asks for help on how to do Y instead of actually asking about X. This invariably results in problems for those trying to give an answer.
In this case, the actual problem, X, appears to be that you will like to have a failover for your backend but would like to avoid spending money on a separate server instance and would like to know what options are available.
The idea of using a cache for this is not completely off but you have to approach and set the cache like a failover server which means it has to be a totally separate and independent system from the backend. This rules out proxy_cache which is intimately linked to the backend.
In your shoes, I will set up a memcached server and configure this to cache your stuff but not ordinarily serve your requests except on a 50x error.
There is a memcached module that comes with Nginx that can be compiled and used but it does not have a facility to add items to memcached. You will have to do this outside Nginx (usually in your backend application).
A guide to setting memcached up can be found here or just do a web search. Once this is up and running, this will work for you on the Nginx side:
server {
location / {
# You will need to add items to memcached yourself here
proxy_pass http://backend;
proxy_intercept_errors on
error_page 502 504 = #failover;
}
location #failover {
# Assumes memcached is running on Port 11211
set $memcached_key "$uri?$args";
memcached_pass host:11211;
}
}
Far better than the limited standard memcached module is the 3rd party memc module from OpenResty which allows you to add stuff directly in Nginx.
OpenResty also has the very flexible lua-resty-memcached which is actually the best option.
For both instances, you will need to compile them into your Nginx and familiarise yourself on how to set them up. If you need help with this, ask a new question here with the OpenResty tag or try the OpenResty support system.
Summary
What you actually need is a failover server.
This has to be separate and independent of the backend.
You can use a caching system as this but it cannot be proxy_cacheif you cannot live with getting cached results for the minimum time of 1 second.
You will need to extend a typical Nginx installation to do this.
It is not working because http_500, http_502, http_503, http_504 codes are expected from backend. In your case 504 is nginx code.
So you need to have the following:
proxy_connect_timeout 10s;
proxy_cache_use_stale ... timeout ...
or
proxy_cache_use_stale ... updating ...
or both.
<---> NGX CACHE - NODE A \
REQUESTS <---> LB <--> <----> SHARED FS (NAS)
<---> NGX CACHE - NODE B /
I'm trying to accomplish something like this, requests are load balanced among several nodes which should serve shared cached content when available.
We're experiencing random MISS results, even though the content was stored in the cache filesystem by one of the other nodes. What we can see from logs is that sometimes node A identifies as HIT content cached by node B but sometimes it doesn't.
According to the documentation https://www.nginx.com/blog/nginx-caching-guide/, the mandatory keys_zone parameter sets up a shared memory zone for storing the cache keys and metadata to quickly determine if a request is a HIT or a MISS without having to go to disk.
This is the relevant part of our configuration:
proxy_cache_path /tmp/nginx_cache levels=1:2 keys_zone=my_zone:10m max_size=10g inactive=60m use_temp_path=off;
: : :
location ~* ^/api/rdPage.aspx {
resolver 10.0.0.1;
add_header X-Cache-Status $upstream_cache_status;
proxy_cache_bypass $http_pragma;
proxy_cache_key "$proxy_host$uri$request_body";
proxy_cache_valid 200 10m;
proxy_ignore_headers Cache-Control Expires Set-Cookie;
proxy_cache_methods POST;
proxy_cache_min_uses 0;
proxy_cache my_zone;
# WITH PROXY LB SERVER
proxy_pass http://api-server/rdPage.aspx$is_args$args;
proxy_headers_hash_bucket_size 128;
proxy_connect_timeout 15;
proxy_send_timeout 15;
proxy_read_timeout 1800;
}
How does the keys_zone "shared" memory zone work for several nodes? If it's in memory, how can it be shared? Any alternatives w/o adding custom modules such as SR Cache to nginx (https://www.nginx.com/resources/wiki/modules/sr_cache/)?
Nginx can not share disk-based cache between nodes. Placing cache on shared disk is a bad idea too, it's a bad design. Any network troubles with shared disk, any high latency due network delay and distribution lock accruing can dramatically decrease nginx performance.
Leave cache on local disk. If you are use multiple nodes as load balancing - you can use cache sharding. Each node will receive part of requests and cache it. If you target is HA, each nodes shoud keep own full cache.
More info about all this cases you can read on nginx site: https://www.nginx.com/blog/shared-caches-nginx-plus-cache-clusters-part-1/ about sharding and https://www.nginx.com/blog/shared-caches-nginx-plus-cache-clusters-part-2/ about HA.
I'm running nginx/ruby-on-rails and I have a simple multipart form to upload files.
Everything works fine until I decide to restrict the maximum size of files I want uploaded.
To do that, I set the nginx client_max_body_size to 1m (1MB) and expect a HTTP 413 (Request Entity Too Large) status in response when that rule breaks.
The problem is that when I upload a 1.2 MB file, instead of displaying the HTTP 413 error page, the browser hangs a bit and then dies with a "Connection was reset while the page was loading" message.
I've tried just about every option there is that nginx offers, nothing seems to work. Does anyone have any ideas about this?
Here's my nginx.conf:
worker_processes 1;
timer_resolution 1000ms;
events {
worker_connections 1024;
}
http {
passenger_root /the_passenger_root;
passenger_ruby /the_ruby;
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
server {
listen 80;
server_name www.x.com;
client_max_body_size 1M;
passenger_use_global_queue on;
root /the_root;
passenger_enabled on;
error_page 404 /404.html;
error_page 413 /413.html;
}
}
Thanks.
**Edit**
Environment/UA: Windows XP/Firefox 3.6.13
nginx "fails fast" when the client informs it that it's going to send a body larger than the client_max_body_size by sending a 413 response and closing the connection.
Most clients don't read responses until the entire request body is sent. Because nginx closes the connection, the client sends data to the closed socket, causing a TCP RST.
If your HTTP client supports it, the best way to handle this is to send an Expect: 100-Continue header. Nginx supports this correctly as of 1.2.7, and will reply with a 413 Request Entity Too Large response rather than 100 Continue if Content-Length exceeds the maximum body size.
Does your upload die at the very end? 99% before crashing? Client body and buffers are key because nginx must buffer incoming data. The body configs (data of the request body) specify how nginx handles the bulk flow of binary data from multi-part-form clients into your app's logic.
The clean setting frees up memory and consumption limits by instructing nginx to store incoming buffer in a file and then clean this file later from disk by deleting it.
Set body_in_file_only to clean and adjust buffers for the client_max_body_size. The original question's config already had sendfile on, increase timeouts too. I use the settings below to fix this, appropriate across your local config, server, & http contexts.
client_body_in_file_only clean;
client_body_buffer_size 32K;
client_max_body_size 300M;
sendfile on;
send_timeout 300s;
From the documentation:
It is necessary to keep in mind that the browsers do not know how to correctly show this error.
I suspect this is what's happening, if you inspect the HTTP to-and-fro using tools such as Firebug or Live HTTP Headers (both Firefox extensions) you'll be able to see what's really going on.