This released on ubuntu with packages nginx and nginx-extras (some modules used additionally that not avail on Alma packages, that’s why ubuntu).
We will store cached files in /cdncache and logs in /var/log/nginx/cdnlogs.
Our CDN URL is yourcdn1.domain.name for site1.dev and yourcdn2.domain.name for site2.dev
We will server static files (mp4,jpg,png,gif,jpeg,js,ico,html,htm,webp,css,mp3,wav,swf,mov,doc,pdf,xls,ppt,docx,pptx,xlsx,ttf,woff,woff2), accept only GET queries and disallow listing for /.
user www-data; worker_processes auto; pid /run/nginx.pid; include /etc/nginx/modules-enabled/*.conf; worker_rlimit_nofile 100000; pcre_jit on; events { use epoll; worker_connections 16000; multi_accept on; } http { # IP whitelist to which no conn/rate restrictions should be applied geo $ip_whitelist { default 0; 127.0.0.1 1; 10.225.1.0/24 1; YOUR.WEBSITE.IP/32 1; } map_hash_bucket_size 256; map $ip_whitelist $limited_ip { 0 $binary_remote_addr; 1 ""; } limit_conn_zone $limited_ip zone=connsPerIP:20m; limit_conn connsPerIP 100; limit_conn_status 429; limit_req_zone $limited_ip zone=reqsPerMinutePerIP:50m rate=500r/m; limit_req zone=reqsPerMinutePerIP burst=700 nodelay; limit_req_status 429; client_max_body_size 64k; client_header_timeout 10s; client_body_timeout 10s; client_body_buffer_size 16k; client_header_buffer_size 4k; send_timeout 10s; connection_pool_size 512; large_client_header_buffers 8 16k; request_pool_size 4k; http2_idle_timeout 60s; http2_recv_timeout 10s; http2_chunk_size 16k; server_tokens off; more_set_headers "Server: My-CDN"; include /etc/nginx/mime.types; variables_hash_bucket_size 128; gzip on; gzip_static on; # searches for the *.gz file and returns it directly from disk (compression is provided by our extra process in the background) gzip_disable "msie6"; gzip_min_length 4096; gzip_buffers 16 64k; gzip_vary on; gzip_proxied any; gzip_types image/svg+xml text/plain text/css application/json application/x-javascript application/javascript text/xml application/xml application/xml+rss text/javascript text/x-component font/truetype font/opentype image/x-icon; gzip_comp_level 4; # If you compile nginx with brotli support uncomment this strings #brotli on; # brotli_static on; # searches for the *.br file and returns it directly from the disk (compression is provided by our extra process in the background) # brotli_types text/plain text/css application/javascript application/json image/svg+xml application/xml+rss; # brotli_comp_level 6; output_buffers 1 32k; postpone_output 1460; sendfile on; sendfile_max_chunk 1m; tcp_nopush on; tcp_nodelay on; keepalive_timeout 10 10; ignore_invalid_headers on; reset_timedout_connection on; open_file_cache max=50000 inactive=30s; open_file_cache_valid 10s; open_file_cache_min_uses 2; open_file_cache_errors on; proxy_buffering on; proxy_buffer_size 16k; proxy_buffers 64 16k; proxy_temp_path /cdncache; proxy_cache_min_uses 2; proxy_ignore_client_abort on; proxy_intercept_errors on; proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504; proxy_redirect off; proxy_connect_timeout 60; proxy_send_timeout 180; proxy_cache_lock on; proxy_read_timeout 10s; # setting up trusted IP subnets to respect X-Forwarded-For header (for multi-level proxy setup) set_real_ip_from 127.0.0.1/32; set_real_ip_from 10.1.2.0/24; real_ip_header X-Forwarded-For; real_ip_recursive on; include /etc/nginx/conf.d/upstreams.conf; include /etc/nginx/sites-enabled/*; }
Next we will specify our upstreams (site, that we will cache) in /etc/nginx/conf.d/upstreams.conf
We will limit max cache size to 4GB. You should create folders /cdncache/site1.dev and /cdncache/site2.dev manually.
upstream site1_cdn { server site1.dev:443 max_conns=50; keepalive 50; keepalive_requests 50; keepalive_timeout 5s; } proxy_cache_path /cdncache/site1.dev levels=1:2 keys_zone=cdn_cache_site1:10m max_size=4g inactive=60m use_temp_path=off; upstream site2_cdn { server site2.dev:443 max_conns=50; keepalive 50; keepalive_requests 50; keepalive_timeout 5s; } proxy_cache_path /cdncache/site2.dev levels=1:2 keys_zone=cdn_cache_site2:10m max_size=4g inactive=60m use_temp_path=off;
/etc/nginx/sites-enables/site1.dev.conf
server { # listen 443 ssl; listen YOUR.CDN.POP.IPV4:443 ssl default_server http2 reuseport deferred backlog=32768; listen [YOUR.CDN.POP.IPV6]:443 ssl default_server http2 reuseport deferred backlog=32768; server_name yourcdn1.domain.name; ssl_certificate /etc/letsencrypt/live/domain.name/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/domain.name/privkey.pem; error_log /var/log/nginx/cdnlogs/site1.dev/error_log; # access_log /var/log/nginx/cdnlogs/site1.dev/access_log; access_log off; # if you use IPv6 just remove ipv6=off resolver 1.1.1.1 ipv6=off valid=30s; location ~* .(mp4|jpg|png|gif|jpeg|js|ico|html|htm|webp|css|mp3|wav|swf|mov|doc|pdf|xls|ppt|docx|pptx|xlsx|ttf|woff|woff2)$ { # location / { proxy_cache cdn_cache_site1; proxy_cache_key $uri$is_args$args; proxy_cache_valid 90d; proxy_pass https://site1.dev; # add_header Access-Control-Allow-Origin https://site1.dev; set $headerCorsAllowOrigin ""; if ($http_origin ~ '^https?://(localhost|yourcdn1\.domain\.name|site1\.dev)') { # set $headerCorsAllowOrigin "$http_origin"; add_header Access-Control-Allow-Origin "$http_origin"; } if ($request_method = 'OPTIONS') { more_set_headers "Access-Control-Allow-Origin: $headerCorsAllowOrigin"; more_set_headers "Access-Control-Allow-Methods: GET, HEAD, OPTIONS"; more_set_headers "Access-Control-Max-Age: 3600"; more_set_headers "Content-Length: 0"; return 204; } # we allow to load content only from the original domain (e.g. it prevents displaying our images on foreign domains) valid_referers none blocked server_names site1.dev; if ($invalid_referer) { more_set_headers "Content-Type: application/json"; return 403 '{"code": 403, "message": "Forbidden Resource - invalid referer"}'; } set $webp ""; set $file_for_webp ""; if ($http_accept ~* webp) { set $webp "A"; } if ($request_filename ~ (.+\.(png|jpe?g))$) { set $file_for_webp $1; } if (-f $file_for_webp.webp) { set $webp "${webp}E"; } if ($webp = AE) { rewrite ^/(.+)$ /webp/$1 last; } limit_except GET { deny all; } } location / { return 403; } }
The same will do for site2.dev /etc/nginx/sites-enabled/site2.dev.conf
server { # listen 443 ssl; listen YOUR.CDN.POP.IPV4:443; listen [YOUR.CDN.POP.IPV6]:443; server_name yourcdn2.domain.name; ssl_certificate /etc/letsencrypt/live/domain.name/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/domain.name/privkey.pem; error_log /var/log/nginx/cdnlogs/domain.name/error_log; # access_log /var/log/nginx/cdnlogs/domain.name/access_log; access_log off; # For enabling ipv6 upstream queries disable ipv6=off resolver 1.1.1.1 ipv6=off valid=30s; location ~* .(mp4|jpg|png|gif|jpeg|js|ico|html|htm|webp|css|mp3|wav|swf|mov|doc|pdf|xls|ppt|docx|pptx|xlsx|ttf|woff|woff2)$ { # location / { proxy_cache cdn_cache_site2; proxy_cache_key $uri$is_args$args; proxy_cache_valid 90d; proxy_pass https://site2.dev; # add_header Access-Control-Allow-Origin https://site2.dev; set $headerCorsAllowOrigin ""; if ($http_origin ~ '^https?://(localhost|yourcdn2\.domain\.name|site2\.dev)') { # set $headerCorsAllowOrigin "$http_origin"; add_header Access-Control-Allow-Origin "$http_origin"; } if ($request_method = 'OPTIONS') { more_set_headers "Access-Control-Allow-Origin: $headerCorsAllowOrigin"; more_set_headers "Access-Control-Allow-Methods: GET, HEAD, OPTIONS"; more_set_headers "Access-Control-Max-Age: 3600"; more_set_headers "Content-Length: 0"; return 204; } # we allow to load content only from the original domain (e.g. it prevents displaying our images on foreign domains) valid_referers none blocked server_names site2.dev; if ($invalid_referer) { more_set_headers "Content-Type: application/json"; return 403 '{"code": 403, "message": "Forbidden Resource - invalid referer"}'; } set $webp ""; set $file_for_webp ""; if ($http_accept ~* webp) { set $webp "A"; } if ($request_filename ~ (.+\.(png|jpe?g))$) { set $file_for_webp $1; } if (-f $file_for_webp.webp) { set $webp "${webp}E"; } if ($webp = AE) { rewrite ^/(.+)$ /webp/$1 last; } limit_except GET { deny all; } } location / { return 403; } }
That’s all. Check configuration with nginx -t and restart nginx with systemctl restart nginx