Skip to main content

Overview

Bitwarden Server requires SSL/TLS certificates for:
  1. HTTPS traffic - Securing client-server communication
  2. Token signing - Identity Server token generation and validation
  3. Data protection - ASP.NET Core data protection keys
Production Requirement: Valid SSL certificates from a trusted Certificate Authority are required for production deployments. Self-signed certificates should only be used for development.

Certificate Types

Web Server Certificates

Used by Nginx/reverse proxy for HTTPS traffic.Format: PEM (.crt + .key) or PFX (.pfx)

Identity Server Certificates

Used for signing OAuth 2.0 / OpenID Connect tokens.Format: PFX with private key

Data Protection Certificates

Used by ASP.NET Core for encrypting cookies and tokens.Format: PFX with private key

Client Certificates

Optional mutual TLS authentication.Format: PFX or PEM

Obtaining Certificates

Free, automated SSL certificates:
# Install certbot
sudo apt-get update
sudo apt-get install certbot

# Generate certificate (standalone mode)
sudo certbot certonly --standalone \
  -d vault.example.com \
  -d api.example.com \
  -d identity.example.com \
  --email [email protected] \
  --agree-tos

# Certificates will be in:
# /etc/letsencrypt/live/vault.example.com/fullchain.pem
# /etc/letsencrypt/live/vault.example.com/privkey.pem

Commercial Certificate Authority

Purchase from trusted CAs like DigiCert, GlobalSign, or Sectigo:
1

Generate CSR

openssl req -new -newkey rsa:4096 -nodes \
  -keyout vault.key \
  -out vault.csr \
  -subj "/C=US/ST=California/L=San Francisco/O=Company/CN=vault.example.com"
2

Submit CSR to CA

Submit the .csr file to your certificate authority and complete domain validation.
3

Download Certificate

Download the signed certificate and intermediate certificates from your CA.
4

Create Certificate Chain

# Combine certificate with intermediate chain
cat vault.crt intermediate.crt > fullchain.pem

Self-Signed (Development Only)

For development and testing:
# Generate self-signed certificate
openssl req -x509 -newkey rsa:4096 -sha256 -nodes \
  -keyout vault.key \
  -out vault.crt \
  -days 365 \
  -subj "/CN=localhost" \
  -addext "subjectAltName=DNS:localhost,DNS:api.localhost,DNS:identity.localhost,IP:127.0.0.1"

# Convert to PFX format
openssl pkcs12 -export \
  -out vault.pfx \
  -inkey vault.key \
  -in vault.crt \
  -password pass:changeme
Self-signed certificates will trigger security warnings in browsers and are not suitable for production.

Web Server Configuration

Nginx

Configure Nginx to use SSL certificates:
nginx.conf
server {
    listen 443 ssl http2;
    server_name vault.example.com;
    
    # SSL Certificate Configuration
    ssl_certificate /etc/ssl/bitwarden/fullchain.pem;
    ssl_certificate_key /etc/ssl/bitwarden/privkey.pem;
    
    # SSL Security Settings
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384';
    ssl_prefer_server_ciphers off;
    
    # SSL Performance
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 10m;
    ssl_session_tickets off;
    
    # HSTS (HTTP Strict Transport Security)
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    
    # Other security headers
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-Frame-Options "SAMEORIGIN" always;
    
    location /api/ {
        proxy_pass http://api:5000/;
        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;
    }
    
    location /identity/ {
        proxy_pass http://identity:5001/;
        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;
    }
}

# Redirect HTTP to HTTPS
server {
    listen 80;
    server_name vault.example.com;
    return 301 https://$server_name$request_uri;
}

Docker Compose

Mount certificates in Docker Compose:
docker-compose.yml
services:
  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
      - ./ssl/fullchain.pem:/etc/ssl/bitwarden/fullchain.pem:ro
      - ./ssl/privkey.pem:/etc/ssl/bitwarden/privkey.pem:ro
    networks:
      - bitwarden

Identity Server Certificates

Identity Server requires a certificate for signing tokens:

Generate Identity Server Certificate

# Generate certificate for token signing
openssl req -x509 -newkey rsa:4096 -sha256 -nodes -days 3650 \
  -keyout identity_server.key \
  -out identity_server.crt \
  -subj "/CN=Bitwarden Identity Server"

# Convert to PFX with password
openssl pkcs12 -export \
  -out identity_server.pfx \
  -inkey identity_server.key \
  -in identity_server.crt \
  -password pass:your_password_here

# Get certificate thumbprint (SHA-1 fingerprint)
openssl x509 -in identity_server.crt -fingerprint -noout | \
  sed 's/SHA1 Fingerprint=//g' | \
  tr -d ':' | \
  tr '[:lower:]' '[:upper:]'

Install Certificate

docker-compose.yml
services:
  identity:
    image: ghcr.io/bitwarden/identity:latest
    volumes:
      - ./ssl/identity_server.pfx:/etc/bitwarden/identity_server.pfx:ro
    environment:
      globalSettings__identityServer__certificatePath: "/etc/bitwarden/identity_server.pfx"
      globalSettings__identityServer__certificatePassword: "your_password_here"

Configure Services

appsettings.json
{
  "globalSettings": {
    "identityServer": {
      "certificateThumbprint": "ABC123DEF456..." // or
      "certificatePath": "/etc/bitwarden/identity_server.pfx",
      "certificatePassword": "your_password_here"
    },
    "dataProtection": {
      "certificateThumbprint": "ABC123DEF456..." // Can be same certificate
    }
  }
}
You can use the same certificate for both Identity Server and Data Protection, or separate certificates for enhanced security.

Certificate Rotation

Web Server Certificates

1

Obtain New Certificate

Generate or renew certificate using Let’s Encrypt or your CA.
2

Update Certificate Files

Replace old certificate files with new ones:
cp new_fullchain.pem /etc/ssl/bitwarden/fullchain.pem
cp new_privkey.pem /etc/ssl/bitwarden/privkey.pem
3

Reload Nginx

docker exec bitwarden-nginx nginx -s reload
# or
docker restart bitwarden-nginx
4

Verify

curl -I https://vault.example.com
openssl s_client -connect vault.example.com:443 -servername vault.example.com

Identity Server Certificates

Rotating Identity Server certificates will invalidate all existing tokens. Plan for a maintenance window.
1

Generate New Certificate

Create new certificate and get thumbprint.
2

Install New Certificate

Install alongside old certificate (don’t remove old one yet).
3

Update Configuration

Update certificateThumbprint in all service configurations.
4

Rolling Restart

Restart services one at a time:
docker restart bitwarden-identity
docker restart bitwarden-api
# Wait for tokens to expire or force logout users
5

Remove Old Certificate

After all tokens have expired, remove the old certificate.

Security Best Practices

Use Strong Key Lengths

  • Minimum 2048-bit RSA
  • Recommended: 4096-bit RSA or ECC P-256

Enable HSTS

Add Strict-Transport-Security header to force HTTPS:
max-age=31536000; includeSubDomains; preload

Disable Weak Protocols

  • Disable SSLv3, TLS 1.0, TLS 1.1
  • Use only TLS 1.2 and TLS 1.3

Monitor Expiration

Set up alerts for certificates expiring within 30 days.

Secure Private Keys

  • Use strong file permissions (600 or 400)
  • Never commit private keys to version control
  • Consider Hardware Security Modules (HSM) for production

Use CAA Records

Add DNS CAA records to prevent unauthorized certificate issuance:
example.com. CAA 0 issue "letsencrypt.org"

Troubleshooting

Symptoms: Certificate with thumbprint 'ABC123' not foundSolutions:
  • Verify certificate is installed in correct store (LocalMachine\My on Windows)
  • Check thumbprint matches (no spaces, correct case)
  • Ensure private key is accessible
  • Check file permissions on certificate files
Symptoms: Clients cannot establish HTTPS connectionSolutions:
  • Verify certificate is valid (not expired)
  • Check certificate chain is complete
  • Ensure private key matches certificate
  • Test with: openssl s_client -connect domain:443
Symptoms: Login fails with invalid token signatureSolutions:
  • Verify Identity Server certificate is configured correctly
  • Check certificate has private key
  • Ensure thumbprint in all services matches
  • Restart services after certificate changes
Symptoms: Browser shows security warningsSolutions:
  • For development: Add certificate to trusted root store
  • For production: Use certificate from trusted CA
  • Ensure certificate includes Subject Alternative Names (SAN)

Testing Certificates

# Verify certificate
openssl x509 -in certificate.crt -text -noout

# Test SSL connection
openssl s_client -connect vault.example.com:443 -servername vault.example.com

# Check certificate expiration
echo | openssl s_client -connect vault.example.com:443 2>/dev/null | \
  openssl x509 -noout -dates

# Verify certificate chain
openssl verify -CAfile /etc/ssl/certs/ca-certificates.crt fullchain.pem

# Test specific cipher
openssl s_client -connect vault.example.com:443 -cipher 'ECDHE-RSA-AES128-GCM-SHA256'

Next Steps

Docker Deployment

Deploy services with SSL configured

Configuration

Configure certificate settings in appsettings.json

Security Best Practices

Additional security hardening

Troubleshooting

Resolve common issues

Build docs developers (and LLMs) love