Skip to content

Instantly share code, notes, and snippets.

@ahankinson
Last active February 24, 2020 15:59
Show Gist options
  • Save ahankinson/a361c19b2c9a8865eda511384cd1fe46 to your computer and use it in GitHub Desktop.
Save ahankinson/a361c19b2c9a8865eda511384cd1fe46 to your computer and use it in GitHub Desktop.
NginX configuration for IIIF Presentation and Image APIs

Some of these configurations may be more useful than others, but basically we have:

  • A single virtual host configuration for your IIIF domain
  • A common nginx.conf file
  • A cache configuration file for setting up a front-end static cache of backend services
  • Some proxy parameters for passing values to upstream services
  • 'Battle-tested' CORS configurations based on feedback from users of Bodleian IIIF services (and where we were getting it wrong!)

The comments in each of the files should provide more specifics about individual configuration directives.

The setup for this assumes that the nginx service sits in front of a number of 'upstream' services that handle specifics for routing IIIF calls to the right place, either images or presentation. In the current setup we have a load balancer that does this routing to several backend services, so the primary purpose of the nginx configuration is to serve as a front-end to that service, caching things up-front and intercepting easy calls (like redirects) so that we do not have to bother the upstream systems.

# set Access-Control-Allow-Origin as *
# You may be wondering why this is included so often in the main IIIF configuration, or why we repeat the contents
# of this in the configuration.
# It's because there are a couple restrictions and specifications we need to meet.
# - OPTIONS requests must be handled with a 204, but you cannot include a file within an IF statement.
# - For IIIF we must send CORS responses for locations that are redirects.
# - There is no way to set this globally without also doing it on each location.
#
# 'always' means we include CORS responses on error responses as well. This is required for javascript to read the error status
add_header "Access-Control-Allow-Origin" "*" always;
add_header "Access-Control-Allow-Methods" "GET, OPTIONS" always;
add_header "Access-Control-Allow-Headers" "Accept,DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range" always;
add_header "Access-Control-Max-Age" 1728000;
# Caching configuration
proxy_cache_path /data/cache/i levels=1:2 keys_zone=iiif:10m max_size=10g inactive=1w use_temp_path=off;
add_header X-Cache-Status $upstream_cache_status;
# cache bypassing for testing purposes
proxy_cache_bypass $http_x_bodl_nocache;
# IIIF cache keys have the version prepended; this is not needed for general caching, though, so
# set the default here.
proxy_cache_key $scheme://$host$uri$is_args$args;
proxy_cache_valid 1d;
proxy_cache_revalidate on;
proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
proxy_cache_background_update on;
proxy_cache_lock on;
upstream load_balancer {
server load.balancer.ip.address;
}
# set up a map object that will read the `Accept:` header and
# map the values to either v2 or v3 static values. This is used as a cache
# key prefix so that we can set up different caches for either the v2 or v3
# responses.
map $http_accept $cache_accept_key {
~/.*(?<vthree>presentation/3).*/ "v3";
~/.*(?<vthree>image/3).*/ "v3";
default "v2";
}
# Virtual host configuration for iiif.bodleian
server {
server_name iiif.hostname.org;
root /var/www/iiif;
listen 443 ssl http2;
listen [::]:443 ssl http2;
keepalive_timeout 70;
ssl_certificate /etc/nginx/ssl/certificate.crt;
ssl_certificate_key /etc/nginx/ssl/certkey.key;
ssl_trusted_certificate /etc/nginx/ssl/certificate.crt;
add_header Strict-Transport-Security "max-age=63072000";
ssl_stapling on;
ssl_stapling_verify on;
ssl_session_tickets on;
ssl_session_timeout 24h;
ssl_session_cache shared:SSL:100m;
ssl_session_ticket_key /etc/nginx/ssl/ticket.key;
ssl_dhparam /etc/nginx/ssl/dhparam.pem;
ssl_prefer_server_ciphers on;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers "... long list of ciphers ...";
resolver 8.8.8.8 8.8.4.4;
# Ignore all location requests that start with a dot.
# Return 404 to make it look like it's not found (otherwise
# a 403 would say it is there, you just can't access it)
location ~ /\. {
deny all;
access_log off;
return 404;
}
# For planned service outages, touch /etc/nginx/service.unavailable and this
# host will return 503, which can be detected and automatically shown on
# the service outage page. To resume service, simply delete that file.
if (-f /etc/nginx/service.unavailable) {
return 503;
}
# Serve a specific page for the 503 error with service outage information.
error_page 503 @custom503;
location @custom503 {
root /var/www/iiif;
rewrite ^(.*)$ /nginx_errors/503.html break;
internal;
}
# Redirect image API requests without an 'info.json' to the same
# request with an info.json. Matches both URIs with and without
# a trailing slash.
location ~ "^/iiif/image/(?P<iiif>[a-f0-9\-]{36})/?$" {
include allow_origin_wildcard;
return 303 /iiif/image/$iiif/info.json;
}
# A request for the root of the server will redirect to an 'info.json'
# resource pointing to IIIF resources available on this service.
location ~ "^/?$" {
include allow_origin_wildcard;
return 303 /info.json;
}
# Forward requests to the load balancers. If the request is an OPTIONS request,
# ensure we serve the data with the appropriate CORS headers and a 204 No Content.
# try_files will allow us to serve static content as first preference, but then
# send the request upstream to our load balancers if it does not match.
location / {
if ($request_method = OPTIONS) {
add_header "Access-Control-Allow-Origin" '*';
add_header "Access-Control-Allow-Methods" "GET, OPTIONS";
add_header "Access-Control-Allow-Headers" "Accept,DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range";
add_header "Access-Control-Max-Age" 1728000;
add_header "Content-Type" "text/plain; charset=utf-8";
add_header "Content-Length" 0;
return 204;
}
include allow_origin_wildcard;
try_files $uri $uri/ @proxy_to_lb;
}
location @proxy_to_lb {
include proxy_params;
include allow_origin_wildcard;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Scheme $scheme;
proxy_pass http://load_balancer;
proxy_cache iiif;
# Responses can differ based on original hostname and the IIIF version requested. Read the accept
# header in the $cache_accept_key map and prepend that to the cache key so we can cache IIIF v2
# and v3 manifests separately.
proxy_cache_key "$cache_accept_key$scheme$host$request_uri";
add_header X-Cache-Status $upstream_cache_status;
proxy_no_cache $upstream_http_x_bodl_missing;
proxy_cache_bypass $http_pragma;
}
}
# Automatically forward non-HTTPS to HTTPS. It is important to ensure the CORS
# headers are set appropriately.
server {
listen [::]:80;
listen 80;
server_name iiif.hostname.org;
location / {
if ($request_method = OPTIONS) {
add_header "Access-Control-Allow-Origin" '*';
add_header "Access-Control-Allow-Methods" "GET, OPTIONS";
add_header "Access-Control-Allow-Headers" "Accept,DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range";
add_header "Access-Control-Max-Age" 1728000;
add_header "Content-Type" "text/plain; charset=utf-8";
add_header "Content-Length" 0;
return 204;
}
include allow_origin_wildcard;
return 301 https://$server_name$request_uri;
}
}
user nginx;
worker_processes auto;
pid /var/run/nginx.pid;
# Load dynamic modules. See /usr/share/nginx/README.dynamic.
include /usr/share/nginx/modules/*.conf;
events {
use epoll;
multi_accept on;
worker_connections 8192;
}
http {
# Define a log format that can track the virtual host.
log_format vhost '$host: $remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'$request_time $upstream_response_time';
access_log /var/log/nginx/access.log vhost buffer=16k if=$log_if;
error_log /var/log/nginx/error.log warn;
charset utf-8;
charset_types text/xml text/plain application/javascript
application/rss+xml application/json application/ld+json;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
# A warning was emitted about increasing the headers hash.
proxy_headers_hash_bucket_size 128;
proxy_buffers 16 16k;
proxy_buffer_size 16k;
open_file_cache max=20000 inactive=20s;
open_file_cache_valid 30s;
open_file_cache_min_uses 2;
open_file_cache_errors on;
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_min_length 10240;
gzip_comp_level 6;
gzip_buffers 16 8k;
gzip_http_version 1.1;
gzip_types text/plain
text/css
application/json
application/ld+json
application/javascript
application/x-javascript
text/xml
application/xml
application/xml+rss
text/javascript
image/svg+xml;
include /etc/nginx/mime.types;
default_type application/octet-stream;
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*.conf;
}
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Scheme $scheme;
proxy_set_header X-Forwarded-Port $server_port;
# proxy settings for all redirects
proxy_redirect off;
proxy_intercept_errors on;
proxy_http_version 1.1;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment