I wanted to see if anyone else has considered the idea of using NGINX caching to keep the load down to a minimum for actual Plex API calls. If you’re doing so, what locations did you decide to cache and why?
Currently I’m running a setup with the following caching and have cut the chatter down to just library reads and status update information. To be fair, the caching of children, related, similar, and items does cause a delay in the watch status and resume status when viewed, however, when you enter the actual video then the playback status is properly reflected.
In case you are wondering the motivation behind the collections, I have a number of collections based on things such as publisher that are regularly displayed.
# Let's give this a better look
log_format plex '#######################################################\n'
'Time [$time_local]\n'
'HTTP Status [$status]\n'
'Plex-Client-Identifier [$http_x_plex_client_identifier]\n'
'Plex-Token [$http_x_plex_token]\n'
'Remote IP [$remote_addr]\n'
'Forwarded-For [$http_x_forwarded_for]\n'
'Referrer [$http_referer]\n'
'Request [$request]\n'
'#######################################################\n';
log_format plex_cache_images '#######################################################\n'
'Time [$time_local]\n'
'HTTP Status [$status]\n'
'Cache Status [Cache:$upstream_cache_status]\n'
'Key [$arg_url$arg_width$arg_height;]\n'
'Request [$request]\n'
'#######################################################\n';
log_format plex_cache_meta '#######################################################\n'
'Time [$time_local]\n'
'HTTP Status [$status]\n'
'Cache Status [Cache:$upstream_cache_status]\n'
'Key [$uri$x_plex_token$content_directory_id]\n'
'Request [$request]\n'
'#######################################################\n';
log_format plex_debug 'Time [$time_local]\n'
'Remote IP [$remote_addr]\n'
'HTTP Status [$status]\n'
'Referrer [$http_referer]\n'
'User-Agent [$http_user_agent]\n'
'Forwarded-For [$http_x_forwarded_for]\n'
'Request [$request]\n'
'Plex-Client-Identifier [$http_x_plex_client_identifier]\n'
'Plex-Device [$http_x_plex_device]\n'
'Plex-Device-Name [$http_x_plex_device_name]\n'
'Plex-Platform [$http_x_plex_platform]\n'
'Plex-Platform-Version [$http_x_plex_platform_version]\n'
'Plex-Product [$http_x_plex_product]\n'
'Plex-Token [$http_x_plex_token]\n'
'Plex-Version [$http_x_plex_version]\n'
'Plex-Nocache [$http_x_plex_nocache]\n'
'Plex-Provides [$http_x_plex_provides]\n'
'Plex-Device-Vendor [$http_x_plex_device_vendor]\n'
'Plex-Model [$http_x_plex_model]\n';
# Setup some variables to use in our cache keys
map $args $x_plex_client_identifier {
"~(^|&)X-Plex-Client-Identifier=(?<temp>[^&]+)" $temp;
}
map $args $x_plex_token {
"~(^|&)X-Plex-Token=(?<temp>[^&]+)" $temp;
}
map $args $content_directory_id {
"~(^|&)contentDirectoryID=(?<temp>[^&]+)" $temp;
}
#
map $args $include_user_state {
"~(^|&)includeUserState=(?<temp>[^&]+)" $temp;
}
# Upstream servers section
upstream plex_upstream {
server 127.0.0.1:32400;
}
upstream tautulli_upstream {
server 127.0.0.1:8181;
}
#Cache
proxy_cache_path /tmp/nginx_cache/images levels=1:2 keys_zone=plex-images:100m max_size=50G;
proxy_cache_path /tmp/nginx_cache/meta levels=1:2 keys_zone=plex-meta:100m max_size=50G;
server {
listen 80 default deferred;
server_name example.com;
return 301 https://$http_host:443$request_uri;
}
server {
listen 443 ssl http2 default deferred;
server_name example.com;
# path to fullchain.pem on local machine
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; # managed by Certbot
# path to privkey.pem
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; # managed by Certbot
gzip on;
gzip_vary on;
gzip_min_length 1000;
gzip_proxied any;
gzip_types text/plain text/css text/xml application/xml text/javascript application/x-javascript image/svg+xml;
gzip_disable "MSIE [1-6]\.";
# Forward real ip and host to Plex
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_set_header Sec-WebSocket-Extensions $http_sec_websocket_extensions;
proxy_set_header Sec-WebSocket-Key $http_sec_websocket_key;
proxy_set_header Sec-WebSocket-Version $http_sec_websocket_version;
# Websockets
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
location / {
proxy_pass https://plex_upstream;
#access_log off;
access_log /var/log/nginx/plex-access.log plex;
error_log /var/log/nginx/plex-error.log;
# Possible way to break cache
# location ~ /library/(metadata|collections)/[0-9]+/userState {
# rewrite ^ https://$http_host:443$uri?includUserState=1 permanent;
# }
location /photo {
access_log /var/log/nginx/plex-cache-images.log plex_cache_images;
proxy_ignore_headers Cache-Control;
add_header X-Cache-Status $upstream_cache_status;
add_header X-Cache-Date $upstream_http_date;
add_header Pragma "public";
add_header Cache-Control "public";
expires 1w;
proxy_cache plex-images;
proxy_cache_key $arg_url$arg_width$arg_height;
proxy_cache_valid 200 301 302 1d;
proxy_pass https://plex_upstream;
}
location /hubs/promoted {
access_log /var/log/nginx/plex-cache-meta.log plex_cache_meta;
proxy_ignore_headers Cache-Control;
add_header X-Cache-Status $upstream_cache_status;
add_header X-Cache-Date $upstream_http_date;
add_header Pragma "public";
add_header Cache-Control "public";
expires 1h;
proxy_cache plex-meta;
proxy_cache_key meta-hubs-promoted$uri$x_plex_token$content_directory_id;
proxy_cache_valid 200 301 302 15m;
proxy_cache_bypass $include_user_state;
proxy_pass https://plex_upstream;
}
# Disabled until I find a way to break the cache when a watch status is changed
# location ~ /library/(metadata|collections)/[0-9]+ {
# access_log /var/log/nginx/plex-cache-meta.log plex_cache_meta;
# proxy_ignore_headers Cache-Control;
# add_header X-Cache-Status $upstream_cache_status;
# add_header X-Cache-Date $upstream_http_date;
# add_header Pragma "public";
# add_header Cache-Control "public";
# expires 1h;
# proxy_cache plex-meta;
# proxy_cache_key meta-item$uri$x_plex_token$content_directory_id;
# proxy_cache_valid 200 301 302 1h;
# proxy_cache_bypass $include_user_state;
# proxy_pass https://plex_upstream;
# }
location ~ /library/(metadata|collections)/[0-9]+/(children|related|similar|items)$ {
access_log /var/log/nginx/plex-cache-meta.log plex_cache_meta;
proxy_ignore_headers Cache-Control;
add_header X-Cache-Status $upstream_cache_status;
add_header X-Cache-Date $upstream_http_date;
add_header Pragma "public";
add_header Cache-Control "public";
expires 1h;
proxy_cache plex-meta;
proxy_cache_key meta-related$uri$x_plex_token$content_directory_id;
proxy_cache_valid 200 301 302 15m;
proxy_cache_bypass $include_user_state;
proxy_pass https://plex_upstream;
}
location ~ /library/(metadata|collections)/[0-9]+/(extras|theme|thumb)$ {
access_log /var/log/nginx/plex-cache-meta.log plex_cache_meta;
proxy_ignore_headers Cache-Control;
add_header X-Cache-Status $upstream_cache_status;
add_header X-Cache-Date $upstream_http_date;
add_header Pragma "public";
add_header Cache-Control "public";
expires 1w;
proxy_cache plex-meta;
proxy_cache_key meta-extras$uri$x_plex_token$content_directory_id;
proxy_cache_valid 200 301 302 1d;
proxy_cache_bypass $include_user_state;
proxy_pass https://plex_upstream;
}
location ~* \.(html|css|jpg|gif|ico|js)$ {
access_log /var/log/nginx/plex-cache-meta.log plex_cache_meta;
proxy_ignore_headers Cache-Control;
add_header X-Cache-Status $upstream_cache_status;
add_header X-Cache-Date $upstream_http_date;
add_header Pragma "public";
add_header Cache-Control "public";
expires 1d;
proxy_cache plex-meta;
proxy_cache_key static$uri;
proxy_cache_valid 200 301 302 1d;
proxy_pass https://plex_upstream;
}
}
}
My next step is to try and make the cache buckets a bit broader by playing with the proxy_cache_keys so it can cache for everyone as a whole instead of for each user on things like posters and what not. Currently it is using the $args as part of it which is not terribly effective, but will be refined as time permits.