Guide to routing to your Bitwarden Server via nginx reverse proxy
Using a Reverse Proxy
In this guide we will cover how to set up a Self-hosted Bitwarden Server, accessed via an Nginx Reverse Proxy.
A reverse proxy is an application that sits between end-users and the servers and services that they wish to access. There are many reason a user may wish to implement a reverse proxy, which can be read about here:
In particular, many users implement reverse proxies so that only a single endpoint is exposed to the internet.
General Overview
In this guide we will create the following setup:
The guide begins with traffic destined for example.com hitting the router, which is configured to forward traffic to ports 80 and 443 of an Nginx reverse proxy. This reverse proxy is hosted on VM#1 (192.168.0.43) on the 192.168.0.0/24 network.
Nginx will forward the traffic depending on the subdomain requested. In the guide and code examples below, 3 different routes are configured:
*.example.com
vault.example.com
webservice.example.com
The wildcard (*) ensures that any traffic NOT attempting to access either vault.example.com or webservice.example.com will be redirected to the static pages being served directly by the Nginx server - e.g., https://example.com, www.example.com and otherservice.example.com will all redirect to these static pages.
VM#2 consists of a standard Bitwarden installation with a self-signed certificate, with the static IP address 192.168.0.44.
Finally, the DockerHost itself (IP 192.168.0.42) is running some containerised services - an example of which is accessible on port 85. VMs #1 and #2, as well as DockerHost, are all running Ubuntu Server, for 3 Ubuntu environments in total.
Certificate Termination Considerations
Bitwarden must be configured to use a TLS certificate. When using Bitwarden in a web-facing configuration, this is terminated at the nginx container on the Bitwarden server itself. The various options available are described in the Help Guide here:
When a reverse proxy server is used, the certificate should instead be terminated at the reverse proxy. Despite this, a self-signed certificate must still be available to Bitwarden, the configuration steps for which are described below.
Certificate Renewal Considerations
With the end-user certificate being handed over to the Nginx server, the LetsEncrypt renewal services used by the Bitwarden server can no longer be used, and the end-user is now responsible for this action.
While this can be handled in many ways, this guide will demonstrate how to use Certbot with a CRON job to perform this task.
Nginx Server Configuration
First we’ll look at the Nginx Server, which in this example is running on Ubuntu 22.04 LTS.
Static IP configuration
This server will need a static IP on the network in order to receive incoming traffic from the router. This is generally be configured with netplan:
https://ubuntu.com/server/docs/network-configuration
Nginx Installation
There are many ways to configure and install Nginx, including building from source, running inside a container via Docker and via APT. Using the APT package manager is probably the simplest way to do so:
sudo apt install nginx
You can check that the installation was successful by running:
nginx -v
On Ubuntu, nginx should automatically be picked up by Systemd and set to run as a service. Some useful commands to manage Nginx are:
nginx -t
tests your nginx configuration file
nginx -s reload
restarts the nginx server, taking into account any changes made to the configuration file
Writing the Nginx Configuration File
The default configuration file location is:
/etc/nginx/nginx.conf
Writing a complete nginx configuration is not within the scope of this article, but comprehensive documentation can be found here:
Many applications also come with suggested nginx configurations, which are an excellent starting point to add code-blocks for other proxy-destinations and virtual-hosts.
In the guide below, the nginx configuration file must firstly be primed, with the 3 destinations defined in server blocks, after which certbot will be able to make the necessary changes to enable SSL termination.
Initial nginx configuration
We first need to provide the server blocks for our host servers - these will log the domains in the file so that certbot can identify them and request certificates. Here is an example configuration file that serves three services:
- A Bitwarden server at vault.example.com - available on .44
- A Webserver at *.example.com - available via the Nginx server at .43
- A Webservice at webservice.example.com - available at port 85 on .42
In the below configuration example, each of these services is represented by a server block, containing instructions for how to handle traffic that reaches nginx requesting those services.
events {
}
http {
include mime.types;
server {
server_name *.example.com;
root /etc/nginx/sites/example;
}
server {
listen 80;
server_name vault.example.com;
location / {
return 301 https://$server_name$request_uri;
}
}
server {
listen 443 ssl;
server_name vault.example.com;
set $upstream https://192.168.1.44:443;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Forwarded $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-For $remote_addr;
client_max_body_size 256m;
client_body_buffer_size 128k;
proxy_connect_timeout 90;
proxy_send_timeout 90;
proxy_read_timeout 90;
proxy_buffers 32 4k;
location / {
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_pass $upstream;
}
}
server {
server_name webservice.example.com;
location / {
proxy_pass http://192.168.1.42:85;
proxy_redirect http://192.168.1.42:85 https://webservice.example.com;
}
}
}
This initial configuration is enough for Certbot to work with.
Note the headers and other variables present in the Bitwarden (vault.example.com) Server block, which are used to ensure that the traffic flowing to the Bitwarden server via the nginx reverse proxy is handled correctly.
Install Certbot
Certbot is a free, open source software tool to automatically generate Let’s Encrypt certificates. Their website is located here: https://certbot.eff.org/
Bitwarden sets up Certbot in Docker to generate LetsEncrypt signed certificates as part of the installation script, and regularly calls this tool as part of updates and the ‘renewcert’ shell command. Because in this example the nginx server is not installed on the same machine as the Bitwarden server, we will need to configure Certbot manually on the machine hosting the nginx reverse proxy server.
The simplest way to install Certbot on Ubuntu is using snap:
sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/bin/certbot
Use Certbot to generate SSL certificates
Certbot can read and modify our initial nginx configuration, based on the server blocks we’ve already configured:
sudo certbot --nginx
When prompted, ask Certbot to generate a certificate for all domains found. You should find that your nginx configuration has been modified to add ssl certificates configurations to all of the locations previously defined:
events {
}
http {
include mime.types;
server {
server_name example.com;
root /etc/nginx/sites/example;
listen 443 ssl; # managed by Certbot
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; # managed by Certbot
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
}
server {
if ($host = vault.example.com) {
return 301 https://$host$request_uri;
} # managed by Certbot
listen 80;
server_name vault.example.com;
location / {
return 301 https://$server_name$request_uri;
}
}
server {
listen 443 ssl;
server_name vault.example.com;
set $upstream https://192.168.1.44:443;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Forwarded $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-For $remote_addr;
client_max_body_size 256m;
client_body_buffer_size 128k;
proxy_connect_timeout 90;
proxy_send_timeout 90;
proxy_read_timeout 90;
proxy_buffers 32 4k;
location / {
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_pass $upstream;
}
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; # managed by Certbot
}
server {
if ($host = example.com) {
return 301 https://$host$request_uri;
} # managed by Certbot
listen 80;
server_name example.com;
return 404; # managed by Certbot
}
server {
if ($host = webservice.example.com) {
return 301 https://$host$request_uri;
} # managed by Certbot
listen 80;
server_name webservice.example.com;
return 404; # managed by Certbot
}
server {
server_name webservice.example.com;
listen 443 ssl; # managed by Certbot
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; # managed by Certbot
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
location / {
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://192.168.1.42:85;
proxy_redirect http://192.168.1.42:85 https://webservice.example.com;
}
}
}
Bitwarden Certificate Configuration - Using a Self Signed Certificate
Even though Bitwarden is being fronted by the nginx server, which is handling certificate termination, it is best practice to configure Bitwarden to use a self-signed certificate in order to encrypt its internal communications. How you do this will depend on whether you are installing Bitwarden from scratch, or converting an existing Bitwarden server to run behind the proxy.
New Bitwarden Installations
If you have not yet installed your Bitwarden server, the installation scripts will take care of generating and configuring a self-signed certificate for you.
Follow the standard installation guide (Linux Standard Deployment | Bitwarden Help Center), but when prompted whether to use a LetsEncrypt certificate in step #5, select no, and no again when asked if you have an SSL certificate to use. The installer will then prompt you to generate a self-signed SSL certificate, at which point please select yes to do so.
Post-install configuration
Post-install configuration steps should be completed as normal, including the setup of admin users and an SMTP server:
Finally, start Bitwarden when you have made all the necessary configuration changes:
Configuring an Existing Bitwarden Installation
If you have an existing Bitwarden server, configured with either a LetsEncrypt or CA provided certificate, this will need to be swapped over to a self-signed certificate once the reverse proxy has been put in place.
Generate and configure Self Signed certificate
Generating and configuring Bitwarden to use a Self Signed certificate is documented at the link below:
Once you have performed these steps, you will need to rebuild and then restart your server using the Bitwarden shell script commands.
Note: If you are moving away from using a SelfSigned certificate managed by LetsEncrypt, Bitwarden will still attempt to renew this, even though it is no longer being used by the Bitwarden Server. You can stop this from happening by deleting or renaming the /bwdata/letsencrypt/live directory (e.g. mv live live_bak)
Note: If you are moving away from using a LetsEncrypt certificate, don’t forget to set the ssl_managed_lets_encrypt variable to false in /bwdata/config.yml
Testing
At this stage, assuming that all has gone well, you should be able to access all 3 different routes via a web-browser! You should also be able to configure your Bitwarden clients to access the server as described below:
Automate Certificate Renewals with Certbot
Finally we can use CRON to automatically renew our SSL certificates on the Docker Host, now that Bitwarden can no longer take care of this task for us.
Certbot comes with this functionality already built in - in order to test your renewals you can use the command
sudo certbot renew –dry-run
After checking that no errors are shown in the output, it is convenient to add a cronjob to renew this on a regular basis.
Although the certificates last for a long time, Certbot recommends running the renewal script regularly. If the certificates are not due to be renewed then no action will be taken, while running regularly will help should a Let’sEncrypt initiated revocation take place for any reason.
It is good practise to choose a random minute to on which to run the scripts (i.e., replace 51 with a random number between 0 and 59):
crontab -e
Add the following line to your crontab
# m h dom mon dow command
51 6 * * * certbot renew --post-hook "nginx -s reload" >/dev/null 2>&1