How to cache NextJS 10.0 images using NGINX - nginx

We would like to launch a NextJS 10 app using NGINX so we use a configuration similar to:
location /_next/static/ {
alias /home/ec2-user/my-app/.next/static/;
expires 1y;
access_log on;
}
It works great, it caches for a year our statics but as we use NextJS images I'm failing to add an expires tag on on-the-fly resized images.
If I do:
location /_next/image/ {
alias /home/ec2-user/my-app/.next/image;
expires 1y;
access_log on;
}
It just returns a 404 on images.
Here is my server part NGINX config :
server {
listen 80;
server_name *.my-website.com;
# root /usr/share/nginx/html;
# root /home/ec2-user/my-app;
charset utf-8;
client_max_body_size 20M;
client_body_buffer_size 20M;
proxy_connect_timeout 600;
proxy_send_timeout 600;
proxy_read_timeout 600;
send_timeout 600;
underscores_in_headers on;
add_header X-Frame-Options SAMEORIGIN always;
add_header X-Content-Type-Options nosniff always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "same-origin" always;
location = /robots.txt {
proxy_pass https://api.my-website.com/robots.txt;
}
location /_next/static/ {
alias /home/ec2-user/my-app/.next/static/;
expires 1y;
access_log on;
}
location / {
# reverse proxy for merchant next server
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_pass_request_headers on;
proxy_cache_bypass $http_upgrade;
proxy_buffering off;
}
}

Here is an example how you can rely of upstream Content-Type header to set up the Expires and Cache-Control headers:
map $upstream_http_content_type $expire {
~^image/ 1y; # 'image/*' content type
default off;
}
server {
...
location / {
# reverse proxy for merchant next server
proxy_pass http://localhost:3000;
...
expires $expire;
}
}
The same way you can tune cache control headers for any other content type of proxied response. The $upstream_http_<name> nginx variable is described here.
Update
To add cache control headers only by specific URIs you can use two chained map blocks:
map $uri $expire_by_uri {
~^/_next/image/ 1y;
default off;
}
map $upstream_http_content_type $expire {
~^image/ $expire_by_uri;
default off;
}
And if you don't expect anything but the images from /_next/image/... URIs, you can just use the
map $uri $expire {
~^/_next/image/ 1y;
default off;
}

Related

Reverse proxy to port 8069 on Engintron issues while it works on standard NGINX setup

I have an Odoo app running on port 8069, and while this setup worked fine in my old server, my new server is using Engintron which seems to have a different method of working with vhosts. The standout issue is that under common_http.conf, this line becomes a duplicate of the vhost needed to run the app but is included in the automatically generated config that gets overridden whenever a new cpanel account is created, deleted, or when Engintron is updated.
What would be the correct way of setting this up properly within Engintron?
common_http.conf
location / {
try_files $uri $uri/ #backend;
}
# This location / ends up getting included in the custom
# vhost which is needed for all of the sites except this Odoo app.
custom_vhost.com.conf
upstream example{
server 127.0.0.1:8069 weight=1 fail_timeout=0;
}
upstream example-chat {
server 127.0.0.1:8072 weight=1 fail_timeout=0;
}
server {
listen [::]:80;
server_name delegates.example.com;
return 301 https://delegates.example.com$request_uri;
}
server {
listen [::]:80;
server_name vendors.example.com;
return 301 https://vendors.example.com$request_uri;
}
server {
listen [::]:80;
server_name example.com;
return 301 https://example.com;
}
server {
listen [::]:80;
server_name *.example.com;
return 301 https://example.com;
}
server {
listen [::]:443 ssl;
server_name pgadmin.example.com;
# well-known_start
location ^~ /.well-known {
add_header Host-Header 192fc2e7e50945beb8231a492d6a8024;
root /home/example/public_html;
}
# well-known_end
ssl_certificate /var/cpanel/ssl/apache_tls/*.example.com/combined;
ssl_certificate_key /var/cpanel/ssl/apache_tls/*.example.com/combined;
add_header Strict-Transport-Security "max-age=31536000; includeSubdomains";
add_header X-Content-Type-Options nosniff;
add_header Cache-Control public;
location / {
deny all;
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_set_header X-Forwarded-Proto https;
proxy_pass http://127.0.0.1:5050;
}
}
server {
listen [::]:443 ssl;
server_name example.com www.example.com;
return 301 https://example.com;
}
server {
listen [::]:443 ssl http2;
server_name vendors.example.com delegates.example.com;
client_max_body_size 200m;
proxy_read_timeout 720s;
proxy_connect_timeout 720s;
proxy_send_timeout 720s;
proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-NginX-Proxy true;
#proxy_set_header X-Odoo-dbfilter ^%d\Z;
proxy_redirect off;
proxy_buffering off;
# well-known_start
location ^~ /.well-known {
add_header Host-Header 192fc2e7e50945beb8231a492d6a8024;
root /home/example/public_html;
}
# well-known_end
ssl_certificate /var/cpanel/ssl/apache_tls/*.example.com/combined;
ssl_certificate_key /var/cpanel/ssl/apache_tls/*.example.com/combined;
access_log /var/log/nginx/odoo.access.log;
error_log /var/log/nginx/odoo.error.log;
# adds gzip options
gzip on;
gzip_types text/css text/plain text/xml application/xml application/javascript application/x-javascript text/javascript application/json text/x-json;
gzip_proxied no-store no-cache private expired auth;
#gzip_min_length 1000;
gzip_disable "MSIE [1-6]\.";
location /longpolling {
proxy_pass http://example-chat;
}
location ~* /web/static/ {
gzip_static on;
proxy_cache_valid 200 90m;
proxy_buffering on;
expires 864000;
add_header Cache-Control public;
proxy_pass http://example;
}
location / {
error_page 403 = https://example.com;
proxy_pass http://example;
proxy_redirect off;
gzip_static on;
}
# The above location becomes a duplicate of the previous default location - which in turn fails the validity of the configuration.
location ~* /web/content/ {
gzip_static on;
proxy_cache_valid 200 90m;
proxy_buffering on;
expires 864000;
add_header Cache-Control public;
proxy_pass http://example;
}
location /web/database/manager {
deny all;
error_page 403 https://example.com;
proxy_pass http://example;
}
}
Since the conf files are added in alphabetical order, and any conflicting or duplicate settings are ignored - I ended up changing the name of the file so that it's included before the other ones. Also made the file immutable with the following command:
chattr +ai 1_custom_vhost.com.conf
I'm quite sure this is not a graceful solution, but it does the job for now.

Use prerender.io with my existing nginx configuration

I am trying to implement Dynamic Rendering for google.
I have a server on Laravel Forge for serving a Nuxt.js application and I want to use prerender.io
Prerender.io gives an nginx config to use but my current config seems a lot different from it. I don't have any experience with it so I am asking for help if there is anyone who can help.
This is my current nginx config
# FORGE CONFIG (DO NOT REMOVE!)
include forge-conf/beta.example.com/before/*;
map $sent_http_content_type $expires {
"text/html" epoch;
"text/html; charset=utf-8" epoch;
default off;
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name beta.example.com;
root /home/forge/beta.example.com/dist;
# FORGE SSL (DO NOT REMOVE!)
ssl_certificate /etc/nginx/ssl/beta.example.com/50331/server.crt;
ssl_certificate_key /etc/nginx/ssl/beta.example.com/50331/server.key;
ssl_protocols TLSv1.2;
ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384;
ssl_prefer_server_ciphers on;
ssl_dhparam /etc/nginx/dhparams.pem;
add_header X-Frame-Options "SAMEORIGIN";
add_header X-XSS-Protection "1; mode=block";
add_header X-Content-Type-Options "nosniff";
charset utf-8;
gzip on;
gzip_types text/plain application/xml text/css application/javascript;
gzip_min_length 1000;
# FORGE CONFIG (DO NOT REMOVE!)
include forge-conf/beta.example.com/server/*;
location / {
expires $expires;
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;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 1m;
proxy_connect_timeout 1m;
proxy_pass http://127.0.0.1:3000; # set the adress of the Node.js
}
access_log off;
error_log /var/log/nginx/beta.example.com-error.log error;
location ~ /\.(?!well-known).* {
deny all;
}
}
# FORGE CONFIG (DO NOT REMOVE!)
include forge-conf/beta.example.com/after/*;
This is how it should look like
https://gist.github.com/thoop/8165802
My main question basically is what happens to the current location / block.
You have two ways to achieve your goal:
Nuxt.js has its own pre-render solution. you could find it here.
If you still want to use prerender.io, here is an example:
I saw that your Nginx is proxying all traffic to http://127.0.0.1:3000, which is presumably a backend written in Node.js. So you could directly update your location / block to use prerender.io to catch everything.
...
location / {
proxy_set_header X-Prerender-Token YOUR_TOKEN;
set $prerender 0;
if ($http_user_agent ~* "googlebot|bingbot|yandex|baiduspider|twitterbot|facebookexternalhit|rogerbot|linkedinbot|embedly|quora link preview|showyoubot|outbrain|pinterest|slackbot|vkShare|W3C_Validator") {
set $prerender 1;
}
if ($args ~ "_escaped_fragment_") {
set $prerender 1;
}
if ($http_user_agent ~ "Prerender") {
set $prerender 0;
}
if ($uri ~* "\.(js|css|xml|less|png|jpg|jpeg|gif|pdf|doc|txt|ico|rss|zip|mp3|rar|exe|wmv|doc|avi|ppt|mpg|mpeg|tif|wav|mov|psd|ai|xls|mp4|m4a|swf|dat|dmg|iso|flv|m4v|torrent|ttf|woff|svg|eot)") {
set $prerender 0;
}
#resolve using Google's DNS server to force DNS resolution and prevent caching of IPs
resolver 8.8.8.8;
if ($prerender = 1) {
#setting prerender as a variable forces DNS resolution since nginx caches IPs and doesnt play well with load balancing
set $prerender "service.prerender.io";
rewrite .* /$scheme://$host$request_uri? break;
proxy_pass http://$prerender;
}
try_files #backend;
}
location #backend {
expires $expires;
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;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 1m;
proxy_connect_timeout 1m;
proxy_pass http://127.0.0.1:3000; # set the adress of the Node.js
}
...

Setting expire headers for static content with nginx

I'm using Nginx in front of Glassfish (java application server) to serve my static content. I'm using following setting to set the expire headers
location /javax.faces.resource/images/ {
proxy_pass http://xx.xxx.xx:8080/javax.faces.resource/images/;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
expires 365d;
add_header Pragma public;
add_header Cache-Control "public";
}
I have also CSS and JS files inside /javax.faces.resource/ and want to set another expire header. How can I achieve this without breaking the setting for images?
Another question: I noticed that sometimes there is many Cache-Control entries in Response header. Is there a possibility to reset the properties instead of using add_header? Thanks
EDITED
server {
listen xx.x.yy.xxx:80;
server_name mydomain.com www.mydomain.com ;
rewrite ^/(.*) https://www.$server_name/$1 permanent;
}
server {
listen xx.x.yy.xxx:443; ## listen for ipv4
server_name www.mydomain.com;
ssl on;
ssl_certificate /etc/ssl/www.mydomain.com.2018.pem;
# Path to an SSL certificate;
ssl_certificate_key /etc/ssl/www.mydomain.com.2018.key;
# Path to the key for the SSL certificate;
client_max_body_size 20M;
access_log /var/log/nginx/mydomain.com.access.log upstreamlog;
error_log /var/log/nginx/mydomain.com.error.log;
rewrite_log on;
location / {
proxy_pass http://xx.x.yy.xxx:8080;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header HTTPS "on";
}
location /nginx_status {
stub_status on;
access_log off;
# allow 1.1.1.1;
# deny all;
}
location /images/ {
proxy_pass http://xx.x.yy.xxx:8080/images/;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
expires 365d;
add_header Pragma public;
add_header Cache-Control "public";
#add_header X-Cache-Status $upstream_cache_status;
#add_header Strict-Transport-Security max-age=15768000;
}
location /resources/desktop/images/ {
proxy_pass http://xx.x.yy.xxx:8080/resources/desktop/images/;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
expires 365d;
add_header Pragma public;
add_header Cache-Control "public";
#add_header X-Cache-Status $upstream_cache_status;
#add_header Strict-Transport-Security max-age=15768000;
}
location /javax.faces.resource/images/ {
proxy_pass http://xx.x.yy.xxx:8080/javax.faces.resource/images/;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
expires 365d;
add_header Pragma public;
add_header Cache-Control "public";
}
}

Iframe 401 Unauthorized NGINX 3CX

I would like to use an iframe of the 3CX webclient in my website. This iframe works fine on Firefox and Internet Explorer but doesn't work on Chrome (the iframe appears but I can't login). When I go to the console on Chrome the error is:
"Failed to load ressource: the server responded with a statuts of 401
(Unautorized)".
When I go in the Network tab, a xhr file is red, here is the capture of file header error :
I own every files of this 3CX server. As you can see 3CX use Nginx, this is the configuration file of Nginx :
#user nobody;
worker_processes auto;
#error_log logs/error.log;
#error_log logs/error.log notice;
#error_log logs/error.log info;
#pid logs/nginx.pid;
events {
worker_connections 10240;
}
http {
map $http_upgrade $connection_upgrade {
default upgrade;
'' $http_connection;
}
include mime.types;
default_type application/octet-stream;
#limit_req_zone $binary_remote_addr zone=perip:50m rate=1000r/s;
#log_format main '$remote_addr - $remote_user [$time_local] "$request" '
# '$status $body_bytes_sent "$http_referer" '
# '"$http_user_agent" "$http_x_forwarded_for"';
#access_log logs/access.log main;
sendfile on;
#tcp_nopush on;
#keepalive_timeout 0;
keepalive_timeout 65;
#gzip on;
server {
add_header X-Frame-Options "SAMEORIGIN";
listen 5000;
listen [::]:5000;
server_name gesika.3cx.fr;
server_tokens off;
access_log off;
error_log nul crit;
allow 192.168.0.0/16;
allow 172.16.0.0/12;
allow 10.0.0.0/8;
allow 127.0.0.1;
allow ::0/0;
deny all;
client_max_body_size 300m;
location /user_images {
expires 1y;
add_header Pragma public;
add_header Cache-Control "public";
root "C:/ProgramData/3CX/Data/Http/wwwroot";
}
location ~ /webclient/.*\.(js|css|woff|woff2|json|mp3)$ {
expires 1y;
add_header Pragma public;
add_header Cache-Control "public";
root "C:/ProgramData/3CX/Data/Http/wwwroot";
}
location ~ index\.html {
add_header Cache-Control "no-cache, no-store, must-revalidate";
add_header Pragma no-cache;
expires 0;
root "C:/ProgramData/3CX/Data/Http/wwwroot";
}
location / {
index index.html;
root C:/ProgramData/3CX/Data/Http/wwwroot;
try_files $uri $uri/ #proxy;
}
location /MyPhone {
alias C:/ProgramData/3CX/Instance1/Data/Http/Interface/MyPhone;
try_files $uri $uri/ #proxy;
}
location #proxy {
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
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_pass http://127.0.0.1:5004;
proxy_buffering off;
}
location /management/Reports {
alias "C:/ProgramData/3CX/Instance1/Data/Http/Reports";
}
}
server {
add_header X-Frame-Options "SAMEORIGIN";
listen 5001 ssl;
listen [::]:5001 ssl;
server_name gesika.3cx.fr;
server_tokens off;
access_log off;
error_log nul crit;
ssl_dhparam Instance1/dhparam.pem;
ssl_session_cache shared:SSL:60m;
ssl_session_timeout 1d;
ssl_stapling on;
ssl_stapling_verify on;
add_header Strict-Transport-Security max-age=15768000;
ssl_certificate Instance1/gesika.3cx.fr-crt.pem;
ssl_certificate_key Instance1/gesika.3cx.fr-key.pem;
ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:!DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA:!ECDHE-RSA-DES-CBC3-SHA:!ECDHE-ECDSA-DES-CBC3-SHA';
ssl_prefer_server_ciphers on;
client_max_body_size 300m;
location /user_images {
expires 1y;
add_header Pragma public;
add_header Cache-Control "public";
root "C:/ProgramData/3CX/Data/Http/wwwroot";
}
location ~ /webclient/.*\.(js|css|woff|woff2|json|mp3)$ {
expires 1y;
add_header Pragma public;
add_header Cache-Control "public";
root "C:/ProgramData/3CX/Data/Http/wwwroot";
}
location ~ index\.html {
add_header Cache-Control "no-cache, no-store, must-revalidate";
add_header Pragma no-cache;
expires 0;
root "C:/ProgramData/3CX/Data/Http/wwwroot";
}
location / {
index index.html;
root "C:/ProgramData/3CX/Data/Http/wwwroot";
try_files $uri $uri/ #proxy;
}
location /MyPhone {
alias "C:/ProgramData/3CX/Instance1/Data/Http/Interface/MyPhone";
try_files $uri $uri/ #proxy;
}
location #proxy {
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
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_pass http://127.0.0.1:5004;
proxy_buffering off;
}
location /management/Reports {
alias "C:/ProgramData/3CX/Instance1/Data/Http/Reports";
}
}
}
As you can see there is add_header X-Frame-Options "SAMEORIGIN";. I have deleted this line. I also tried to replace it with this add_header X-Frame-Options "ALLOW-FROM *"; but it still doesn't work. I didn't forget to reload my server.
I tried to add this in the config file :
location ~* \.(eot|ttf|woff|woff2)$ {
add_header Access-Control-Allow-Origin *;
}
My iframe works on Chrome when I create a page in the same domain of the 3CX Server. So the problem is all about cross domain origin (CORS).
Thank you in advance to any one who may be able to help me to fix this problem.
change shared header files setting in 3cx nginx conf folder
You have few paramter like x-frame and some ancestor settings either put your website domain or remove them so that it will allow all to access.

Nginx setting expires header with proxy

I have the following base Nginx configuration (pre-installed Ghost platform on DigitalOcean droplet):
server {
listen 80;
server_name xxx.com;
client_max_body_size 10M;
location / {
proxy_pass http://localhost:2368/;
proxy_set_header Host $host;
proxy_buffering off;
}
}
Now I tried to set the following expiry header for my assets but without success:
location ~ ^/assets/ {
expires 30d;
add_header Pragma public;
add_header Cache-Control "public";
}
Based on the information I've found, Nginx only uses one location path at a time so have to copy the proxy_* parameters inside the assets location block. If I just copy them I get an error (regex with proxy_pass) that can be solved by rewriting the URL before passing it to the proxy. I already did some experiments with that but I don't get it to work either.
Does anyone have an example of how to set expiry headers when a proxy_pass is present? I simply want all files under xxx.com/assets/ to have the proper expiry date.
location /assets/ {
expires 30d;
add_header Pragma public;
add_header Cache-Control "public";
proxy_pass http://localhost:2368/assets/;
# or proxy_pass http://localhost:2368;
proxy_set_header Host $host;
proxy_buffering off;
}
Nginx doc for proxy_pass say that:
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.
I your case /assets/ get replaced by / (which is an URI). To avoid that either use proxy_pass with URI equal to location prefix (proxy_pass http://localhost:2368/assets/;) or don't use URI at all (proxy_pass http://localhost:2368;). But in latter case nginx will proxy unnormalized URI.
Managed to solve it by using the script below (note the /assets/ after the proxy).
server {
listen 80;
server_name xaviertalpe.be;
client_max_body_size 10M;
location /assets/ {
expires 30d;
add_header Pragma public;
add_header Cache-Control "public";
proxy_pass http://localhost:2368/assets/;
proxy_set_header Host $host;
proxy_buffering off;
}
location / {
proxy_pass http://localhost:2368/;
proxy_set_header Host $host;
proxy_buffering off;
}
}

Resources