Child pages
  • Nginx as a proxy
Skip to end of metadata
Go to start of metadata

The following describes nginx as a reverse proxy in front of CouchDB. Typically you use such a proxy when you want to:

  • rewrite URLs
  • load balance (for that you actually might consider HAProxy)
  • Access restrictions
  • Identity management outside of CouchDB
  • Hosted CouchDB where only Ports 80,443 are open to public access

This page describes different setups. It is based on the previous wiki with contemporary updates and insights.

Prerequisites

The examples below are working on Linux based servers where nginx is installed as a service. Typically all site configurations are stored in /etc/nginx/sites-available with symlinks pointing to /etc/nginx/sites-enabled. A few tips when dealing with nginx, it is particular for its configuration files.

  • Always have a backup copy of the configuration file. I usually make a copy inside the sites-available directory, since these files don't impact what gets loaded
  • Before activating any of the changes with sudo service nginx restart, make sure you have tested the configuration using sudo nginx -t. This test command will highlight potential issues, so you don't end with a http server down.

Simple proxy setup

This setup will proxy all incoming requests on port 80 to CouchDB.

Simple proxy
add_header X-Clacks-Overhead "GNU Terry Pratchett";
location / {
    proxy_pass http://localhost:5984;
    proxy_redirect off;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

Continuous Replication

When you plan to use continuous replication, the _changes url needs special attention to work. Add this location configuration:

Continuous Replication
location ~ ^/(.*)/_changes {
    proxy_pass http://localhost:5984;
    proxy_redirect off;
    proxy_buffering off;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

Reverse proxy for a sub directory

A common scenario is to redirect only a certain subdirectory to your couchDB database, so static content (html, css, js) can be served by nginx and/or other services can be provided. The configuration is quite similar to the basic setup, the additional entry is the rewrite command:

Reverse proxy for subdirectory
location /couchdb {
    rewrite /couchdb/(.*) /$1 break;
    proxy_pass http://localhost:5984;
    proxy_redirect off;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

Authentication using nginx

A user authenticates against the nginx authentication (basic in this case) and the result of a successful authentication is handed over to CouchDB, so you don't get a double prompt using Proxy Authentication To make this work, check your couchDB's local.ini file for the following authentication settings:

local.ini
[httpd]
authentication_handlers = {couch_httpd_oauth, oauth_authentication_handler}, {couch_httpd_auth, proxy_authentication_handler}, {couch_httpd_auth, cookie_authentication_handler}, {couch_httpd_auth, default_authentication_handler}

Note: in older versions of couchDB the spelling was actually proxy_authentification_handler.

 

Authentication
location /couchdb {
    auth_basic "Restricted";
    auth_basic_user_file htpasswd;
    rewrite /couchdb/(.*) /$1 break;
    proxy_pass http://localhost:5984;
    proxy_redirect off;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
	proxy_set_header Authorization "";
	add_header X-Auth-CouchDB-UserName $remote_user;
	# not used here: X-Auth-CouchDB-Roles X-Auth-CouchDB-Token
 }

Managing roles

When authentication is passed from the outside, couchDB doesn't lookup roles in the _users database, they need to be provided using the X-Auth-CouchDB-Roles header. One way to generate this header depending on the authenticated user is the map module. The map file is a simple text file with key and value space separated and terminated by a semicolon; A sample could look like this:

nginx couchroles.map
johndoe user,defendant;
janedoe user,defendant;
dredd user,judge,executioner;
docholiday user,judge;

you then include it in your nginx configuration above the server:

# define the lookup
map $remote_user $couchroles {
    include couchroles.map;
}

# Your regular server goes here
server {
    location /couchdb {
       auth_basic "Restricted";
       auth_basic_user_file htpasswd;
       rewrite /couchdb/(.*) /$1 break;
       proxy_pass http://localhost:5984;
       proxy_redirect off;
       proxy_set_header Host $host;
       proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
	   proxy_set_header Authorization "";
	   add_header X-Auth-CouchDB-UserName $remote_user;
	   if ($couchroles) {
		   add_header X-Auth-CouchDB-Roles $couchroles;
       }
    }
}

Unfortunately the map file is only read at a nginx restart, so whenever you change/recreate that file you need to restart nginx.

todo: describe  tokens

nginx as SSL proxy

This section differs most from the original entry. For a full discussion of the settings check Raymii.org's excellent post. In a nutshell: Use contemporary protocols, so no SSL v3 and use only strong ciphers. The starting poing was the set of recommendations with a few changes. The example shows a full-site redirect, TLS enforced. Adjust it to your individual needs:

nginx as SSL proxy
 server {
    listen 80 default_server;
    listen [::]:80 default_server ipv6only=on;
    server_name yourservername.com;
    return 301 https://$server_name$request_uri;
}

# HTTPS server
server {
    ssl on;
    ssl_certificate /etc/ssl/yourcert.pem;
    ssl_certificate_key /etc/ssl/yourkey.key;
    ssl_trusted_certificate /etc/ssl/certsinthemiddle.pem;
    ssl_dhparam /etc/ssl/dhparams.pem;
    ssl_session_timeout 5m;
    ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH";
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_prefer_server_ciphers on;
    # Remove the # from the following line once you are sure everything works
	# add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload";
    add_header X-Frame-Options DENY;
    add_header X-Content-Type-Options nosniff;
    add_header X-Clacks-Overhead "GNU Terry Pratchett";
    ssl_stapling on;
    ssl_stapling_verify on;
    resolver 8.8.4.4 8.8.8.8 valid=300s;
    resolver_timeout 5s;        

    listen 443 default_server;
    listen [::]:443 default_server ipv6only=on;
    server_name yourservername.com;

    location / {
            proxy_pass http://localhost:5984;
            proxy_redirect off;
            proxy_set_header Host $host;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_set_header X-Forwarded-Ssl on;
    }

    location ~ ^/(.*)_changes {
            proxy_pass http://localhost:5984;
            proxy_redirect off;
            proxy_buffering off;
            proxy_set_header Host $host;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

Remarks on SSL

  • Anything Apple (Safari, curl on OS/X, iOS, and most notably replication on OS/X crashes nginx (with an very fast reboot) when the parameter ssl_session_cache shared:SSL:10m; is set. So don't use it

  • I haven't tested the effects of ssl_session_tickets off; Needs more attention, so it's not part of the configuration above

  • Line 3 & 30: replace it with your own server name

  • Line 10/11: these are your certificates. Replace the name accordingly

  • Line 12: This should be the intermediate certificate from your certificate provider

  • Line 13: You need to generate that file for better PFS for your Diffie Hellman Ephemeral Parameters. Use sudo openssl dhparam -out /etc/ssl/certs/dhparam.pem 4096 to generate the file

  • Line 15: excludes weak ciphers. If your clientele still uses older IE versions that might not work Check this for an extended cipher list (not recommended)
  • Line 16: disabled SSLv3
  • Line 17: don't let the browser decide the cipher
  • Line 25: This is Google's DNS. Replace it with OpenDNS or your own if you feel like it
  • Test your TLS setup once completed using SSLLabs