Setting Up Nginx for CATMAID¶
We made good experience using Nginx together with uWSGI and this setup will get be explained in more detail below. CATMAID will of course work with other web-servers and UWSGI servers as well. Further down some information on alternative setups using Gevent or Gunicorn are briefly discussed.
The installation instructions provided here, assume that you have set up the database and Django as described in the standard installation instructions.
Setup based on Nginx and uWSGI¶
uWSGI is a versatile WSGI server written in C, and can serve as the middle layer between Nginx and CATMAID. It works well with connection pooling and communicates efficiently with Nginx.
Install nginx, on Ubunto this would be:
sudo apt-get install nginx
Being in CATMAID’s
virtualenv
, install uwsgi:pip install uwsgi
Create a new configuration file for
uwsgi
calledcatmaid-uwsgi.ini
in CATMAID’sdjango/projects/mysite/
folder:; uWSGI instance configuration for CATMAID [uwsgi] virtualenv = <path-to-virtual-env> chdir = <catmaid-path>/django socket = /var/run/catmaid/uwsgi.socket mount = /<catmaid-relative-url>=<catmaid-path>/django/projects/mysite/django.wsgi manage-script-name = true workers = 2 threads = 2 disable-logging = true
Important: there should not be a trailing slash after the mountpoint
/<catmaid-relative-url>
above. Note also that each thread of each worker will typically have one database connection open. This means Postgres will try to allocate a total of about workers * threads * work_mem (seepostgresql.conf
). Make sure you have enough memory available here.Make sure that the
socket
directory from your.ini
file (/var/run/catmaid/
above) exists and is readable and writable by the user that will runuwsgi
. You now should be able to start uWSGI manually, running it as the current user:uwsgi --ini <catmaid-path>/django/projects/mysite/catmaid-uwsgi.ini
Also note that Nginx needs to be able to access the created
socket
file to communicate with uWSGI. Either you runuwsgi
as the user running Nginx (typicallywww-data
) or you give the Nginx user access on the file, e.g. by using aSetGID
sticky bit on thesocket
folder so that all files created in it have automatically the default group of the Nginx running user assigned (typicallywww-data
).Here is a sample nginx configuration file, where
<catmaid-relative-url> = /catmaid
(replace this with/
if you don’t run in a subdirectory). At the end of this chapter you will find a more complete example configuration:server { listen 80; server_name <CATMAID-HOST>; # Give access to Django's static files location /catmaid/static/ { alias <CATMAID-PATH>/django/static/; } # Route all CATMAID Django WSGI requests to uWSGI location /catmaid/ { include uwsgi_params; uwsgi_pass unix:///var/run/catmaid/uwsgi.socket; } }
Note
To serve static files, Nginx needs execute permission on every directory in
the path to those files (<CATMAID-PATH>/django/static
in example above).
To check this, the namei
command can be very helpful, because it can list
permissions for each path component when called like this:
namei -l <CATMAID-PATH>/django/static
.
Also, it is easy to miss, but important that the the relative URL in the
mount
line of the uWSGI configuration in step 3 has to be exactly the
same as the uWSGI location block in the Nginx configuration in step 5,
including whether there is an ending slash character.
CORS¶
Serving image data works the same way as serving CATMAID static data. If the image data is going to be accessed from websites that aren’t served from the same server, CORS has to be set up. CORS stands for Cross Origin Resource Sharing and manages security of web-clients accessing a particular resource. The same has to be done if a CATMAID API should be accessible by other CATMAID servers.
In its simplest form, this can be done by adding the following line to an Nginx
location
block. It’s only this simple though if no basic HTTP authentication is
in use:
Access-Control-Allow-Origin *
Without this header, only a CATMAID instance served from the same domain name as the image data will be able to access it. If the image data or CATMAID server should be accessed by CATMAID instances served on other domains, this header is required.
In order to support basic HTTP authentication and allow caching of preflight
requests, consider the following more complete example, which can be added to
any location
block:
# Require HTTP Bsic Auth, except for OPTIONS requests. This is needed
# for CORS preflight requests. If no HTTP basic auth is wanted, this block
# can be skipped.
limit_except OPTIONS {
auth_basic "Restricted";
auth_basic_user_file /path/to/logins;
}
# Allow any origin (be more restrictive if wanted)
add_header 'Access-Control-Allow-Origin' '*' always;
# Credentials can be cookies, authorization headers or TLS client certificates
add_header 'Access-Control-Allow-Credentials' 'true' always;
# What methods should be allowed when accessing the resource in response to a preflight request
add_header 'Access-Control-Allow-Methods' 'GET, POST, PATCH, PUT, DELETE, OPTIONS' always;
# Access-Control-Allow-Headers response header is used in response to a preflight request to indicate which HTTP headers can be used during the actual request.
add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,X-Authorization,Authorization' always;
# Preflighted requests. Headers from above are repeated, because of the
# new context being created due to the return statement (causing above
# headers to not be visible).
if ($request_method = 'OPTIONS' ) {
# We need to re-add these headers, because the return statement in the if-block causes this to be a different context.
add_header 'Access-Control-Allow-Origin' '*' always;
add_header 'Access-Control-Allow-Credentials' 'true' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, PATCH, PUT, DELETE, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,X-Authorization,Authorization' always;
# Tell client that this pre-flight info is valid for 20 days
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain charset=UTF-8';
add_header 'Content-Length' 0;
return 204;
}
Note that this allows any other client to access the API, if added to the
regular WSGI location
block. It also makes sure preflight requests are
cached.
Image data¶
Image data often is supposed to be accessed from many different clients, some of which aren’t originating from the same domain name the images are hosted. In order to make this as seamless as possible, CORS needs to be set up (see previous section). A typical tile data location block could look like the example below. There a tile is looked up and if not found, a black default tile is returned instead:
location ~ /tiles/dataset/(.*)$ {
# Try to open path to tile, fallback to black.jpg for non-existent tiles.
try_files /data/dataset/tiles/$1 /data/dataset/tiles/black.jpg =404;
expires max;
add_header Cache-Control public;
# CORS header to allow cross-site access to the tile data
add_header Access-Control-Allow-Origin *;
# Logging usually not needed
access_log off;
}
Besides adding the CORS header, caching is also set to be explicitly allowed, which might be helpful for data that doesn’t change often.
Of course, like with other static files, Nginx must be able able read those files and it needs execute permissions on every directory in the path to the image data.
Process management with Supervisor¶
Depending on your setup, you might use custom scripts to run a WSGI server,
Celery or other server components. In this case, process management has to be
taken care of as well, so that these scripts are run after a e.g. a server
restart. One way to do this is using supervisord
. We found it to be
reliable, flexible and easy to configure with multiple custom scripts. For each
program or program group a new configuration file has to be created:
/etc/supervisor/conf.d/<name>.conf
Such a configuration file can contain information about individual programs and groups of them (to manage them together). Below you will find an example of a typical setup with a uWSGI start script and a Celery start script, both grouped under the name “catmaid”:
[program:catmaid-app]
command = /home/catmaid/catmaid-server/django/env/bin/uwsgi --ini /opt/catmaid/django/projects/mysite/catmaid-uwsgi.ini
user = www-data
stdout_logfile = /home/catmaid/catmaid-server/django/projects/mysite/uwsgi.log
redirect_stderr = true
stopsignal = INT
[program:catmaid-celery]
command = /home/catmaid/catmaid-server/django/projects/mysite/run-celery.sh
user = www-data
numprocs = 1
stdout_logfile = /home/catmaid/catmaid-server/django/projects/mysite/celery.log
redirect_stderr = true
[group:catmaid]
programs=catmaid-app,catmaid-celery
This of course expects a CATMAID instance installed in the folder
/opt/catmaid/
. The stopsignal = INT
directive is needed for uwsgi
,
because it interprets Supervisor’s default SIGTERM
as “brutal reload”
instead of stop. An example for a working run-celery.sh
script can be found
:ref:`here <celery_supervisord>`__. With the configuration and the scripts in
place, supervisord
can be instructed to reload its configuration and start
the catmaid group:
$ sudo supervisorctl reread
$ sudo supervisorctl update
$ sudo supervisorctl start catmaid:
For changed configuration files also both reread
and update
are
required.
Maintenance mode¶
A simple way to display a maintenance mode page in case of an unreachable WSGI server can be configured with the help of Nginx. First, a simple HTML error page is made available as named location block. The CATMAID repo includes an example. The main CATMAID entry location block then references the maintenance location in the case of an unreachable upstream server:
location / {
# Handle error pages
location @maintenance {
root /home/catmaid/catmaid-server/docs/html;
rewrite ^(.*)$ /maintenance.html break;
}
location /tracing/fafb/v14/ {
error_page 502 503 504 @maintenance;
include uwsgi_params;
uwsgi_pass catmaid-fafb-v14;
expires 0;
# Add optional CORS header
}
}
Example configurations¶
This shows a more complete example configuration that we have used in a similar
form in production, including support for WebSockets (/channels/
endpoint).
The CORS config above is made available as /etc/nginx/snippets/cors.conf
and
included in the CATMAID config:
server {
listen 443 ssl http2;
server_name <CATMAID-HOST>;
ssl_certificate <CERT-PATH>;
ssl_certificate_key <CERT-KEY-PATH>;
# Force browsers to keep using https instead of http
add_header Strict-Transport-Security "max-age=604800";
location ~ /tiles/dataset/(.*)$ {
# Try to open path to tile, fallback to black.jpg for non-existent tiles.
try_files /data/dataset/tiles/$1 /data/dataset/tiles/black.jpg =404;
expires max;
add_header Cache-Control public;
# CORS header to allow cross-site access to the tile data
add_header Access-Control-Allow-Origin *;
# Logging usually not needed
access_log off;
}
location /path/to/catmaid/static/ {
alias /home/catmaid/catmaid-server/django/static/;
}
location /path/to/catmaid/files/ {
alias /home/catmaid/catmaid-server/django/files/;
}
location /path/to/catmaid/channels/ {
proxy_pass http://catmaid-fafb-v14-asgi/channels/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
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-Host $server_name;
}
location /path/to/catmaid/ {
error_page 502 503 504 @maintenance;
# Use default uWSGI params and unix socket
include uwsgi_params;
uwsgi_pass unix:///var/run/catmaid/uwsgi.socket;
# No caching
expires 0;
add_header Cache-Control no-cache;
# We need to allow larger requests for our setup (long neuron lists).
client_max_body_size 20m;
# Include open CORS config
include snippets/cors.conf;
}
# Handle error pages
location @maintenance {
root /home/catmaid/public_html;
rewrite ^(.*)$ /502.html break;
}
}
In order to makue sure, the in-memory filesystem folder
/run/catmaid/uwsgi.socket
is available after a reboot, the following can be
added to /etc/rc.local
:
if [ ! -d /var/run/catmaid ]; then
mkdir /var/run/catmaid/
chown www-data:www-data /var/run/catmaid/
chmod 755 /var/run/catmaid/
fi
# Make sure to exit rc.local with success code
exit 0
The following uWSGI configuration allows zero-downtime updates and includes a
statistics socket for uwsgi-top
:
;uWSGI instance configuration for CATMAID
[uwsgi]
chdir = /home/catmaid/catmaid-server/django/
virtualenv = /home/catmaid/catmaid-server/django/env
pidfile = /var/run/catmaid/uwsgi.pid
chmod-socket = 666
socket = /var/run/catmaid/uwsgi.socket
mount = /path/to/catmaid=/home/catmaid/catmaid-server/django/projects/mysite/django.wsgi
manage-script-name = true
uid = www-data
gid = www-data
#plugins = python
workers = 8
threads = 2
disable-logging = true
master = true
# POST buffering
post-buffering = 8192
# Stats
stats = /var/run/catmaid/uwsgi-stats.socket
memory-report = true
# During deploy, old and new master share the same socket. With vacuum=true,
# old master would delete it during shutdown.
vacuum = false
# CATMAID in started in lazy-apps mode, i.e. each worker has a full copy of
# the code in memory. Workers are managed by a master process (no emperor).
master = true
lazy-apps = true
# Use this file as a flag to indicate the uwsgi process is ready to accept
# connections. The file can be looked up during deploy, but it has no meaning
# afterwards. Even there, it is not strictly necessary. It's only a safety
# check.
hook-accepting1-once = write:/var/run/catmaid/catmaid.ready ok
hook-as-user-atexit = unlink:/var/run/catmaid/catmaid.ready
# Create two FIFO slots that we can switch between during runtime. A switch is
# done by sending [0,10] to the current FIFO, by default the first (0) is
# selected.
master-fifo = /var/run/catmaid/new_instance.fifo
master-fifo = /var/run/catmaid/running_instance.fifo
# If there is a running instance, terminate it as soon as the first worker is
# ready to accept connections.
if-exists = /var/run/catmaid/running_instance.fifo
hook-accepting1-once = writefifo:/var/run/catmaid/running_instance.fifo q
endif =
# On start-up, switch from the initial new_instance fifo queue to the
# new_instance queue, by providing the new fifo's index (1) and update the PID
# file (P).
hook-accepting1-once = writefifo:/var/run/catmaid/new_instance.fifo 1P
Like in the initial example, the Supervisor config ties all programs together, this time including the ASGI server Daphne:
[program:catmaid-server-uwsgi]
directory = /home/catmaid/catmaid-server/django/projects/
command = /home/catmaid/catmaid-server/django/env/bin/uwsgi --ini /etc/uwsgi/apps-available/catmaid-server.ini
user = www-data
stdout_logfile = /var/log/catmaid/catmaid-server.log
redirect_stderr = true
stopsignal = INT
[program:catmaid-server-daphne]
directory = /home/catmaid/catmaid-server/django/projects/
command = /home/catmaid/catmaid-server/django/env/bin/daphne --unix-socket=/var/run/catmaid/daphne.sock --access-log - --proxy-headers mysite.asgi:application
user = www-data
stdout_logfile = /var/log/catmaid/daphne-server.log
redirect_stderr = true
[program:catmaid-server-celery]
directory = /home/catmaid/catmaid-server/django/projects/
command = /home/catmaid/catmaid-server/django/env/bin/celery -A mysite worker -l info --pidfile=/var/run/catmaid/celery.pid
user = www-data
numprocs = 1
stdout_logfile = /var/log/catmaid/celery.log
redirect_stderr = true
[program:catmaid-server-celery-beat]
directory = /home/catmaid/catmaid-server/django/projects/
command = /home/catmaid/catmaid-server/django/env/bin/celery -A mysite beat -l info --pidfile=/var/run/catmaid/celery-beat.pid --schedule=/var/run/catmaid/celery-beat-schedule-catmaid-server
user = www-data
numprocs = 1
stdout_logfile = /var/log/catmaid/celery-beat.log
redirect_stderr = true
[group:catmaid-server]
programs=catmaid-server-uwsgi,catmaid-server-daphne,catmaid-server-celery,catmaid-server-celery-beat