Bitwarden stores organization and personal credentials, making it a high-value target. Self-hosted installations must apply hardening from day one. This guide covers security best practices for Linux DevOps teams.
Bitwarden security encompasses:
Keep your Linux server patched:
# Debian/Ubuntu
sudo apt-get update && sudo apt-get upgrade -y
sudo apt-get dist-upgrade -y
# Enable automatic security updates
sudo apt-get install -y unattended-upgrades
sudo dpkg-reconfigure --priority=low unattended-upgrades
# RHEL/Rocky/AlmaLinux
sudo dnf update -y
sudo dnf install -y dnf-automatic
sudo systemctl enable --now dnf-automatic-install.timer
Apply sysctl security settings in /etc/sysctl.d/99-bitwarden-security.conf:
# Disable IP forwarding
net.ipv4.ip_forward = 0
# Disable source routing
net.ipv4.conf.all.accept_source_route = 0
net.ipv4.conf.default.accept_source_route = 0
# Enable SYN flood protection
net.ipv4.tcp_syncookies = 1
# Ignore ICMP broadcasts
net.ipv4.icmp_echo_ignore_broadcasts = 1
# Ignore bogus ICMP errors
net.ipv4.icmp_ignore_bogus_error_responses = 1
# Log martian packets
net.ipv4.conf.all.log_martians = 1
net.ipv4.conf.default.log_martians = 1
# Disable IPv6 if not needed
net.ipv6.conf.all.disable_ipv6 = 1
net.ipv6.conf.default.disable_ipv6 = 1
Apply settings:
sudo sysctl -p /etc/sysctl.d/99-bitwarden-security.conf
Create dedicated service account:
# Create bitwarden user
sudo useradd --system --no-create-home --shell /usr/sbin/nologin bitwarden
# Set proper ownership
sudo chown -R bitwarden:bitwarden /opt/bitwarden
sudo chmod -R 750 /opt/bitwarden
# Install UFW
sudo apt-get install -y ufw
# Set default policies
sudo ufw default deny incoming
sudo ufw default allow outgoing
# Allow SSH (change port if using non-standard)
sudo ufw allow 22/tcp
# Allow HTTP/HTTPS
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
# Enable UFW
sudo ufw enable
# Check status
sudo ufw status verbose
# Install firewalld
sudo dnf install -y firewalld
# Start and enable
sudo systemctl enable --now firewalld
# Add services
sudo firewall-cmd --permanent --add-service=ssh
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
# Reload
sudo firewall-cmd --reload
# List rules
sudo firewall-cmd --list-all
Install and configure fail2ban for Bitwarden:
# Install fail2ban
sudo apt-get install -y fail2ban
# Create Bitwarden jail
sudo cat > /etc/fail2ban/jail.d/bitwarden.local << 'EOF'
[bitwarden]
enabled = true
port = http,https
filter = bitwarden
logpath = /opt/bitwarden-lite/data/logs/*.log
/var/log/bitwarden/*.log
maxretry = 5
bantime = 3600
findtime = 600
EOF
# Create filter
sudo cat > /etc/fail2ban/filter.d/bitwarden.conf << 'EOF'
[Definition]
failregex = ^.*Failed login attempt.*IP: <HOST>.*$
^.*Invalid password.*IP: <HOST>.*$
^.*Invalid two-factor code.*IP: <HOST>.*$
^.*Account locked.*IP: <HOST>.*$
ignoreregex =
EOF
# Restart fail2ban
sudo systemctl restart fail2ban
# Check status
sudo fail2ban-client status bitwarden
Apply security options to all Docker Compose services:
services:
bitwarden-lite:
image: ghcr.io/bitwarden/lite:latest
# Run as non-root user
user: "1000:1000"
# Security options
security_opt:
- no-new-privileges:true
# Drop all capabilities, add only needed
cap_drop:
- ALL
# cap_add:
# - NET_BIND_SERVICE # Only if needed
# Read-only filesystem (where applicable)
read_only: false # Must be false for Lite to write data
# Temporary filesystems
tmpfs:
- /tmp:noexec,nosuid,size=100m
- /run:noexec,nosuid,size=50m
# Resource limits
deploy:
resources:
limits:
cpus: '1.0'
memory: 1G
reservations:
cpus: '0.25'
memory: 256M
# Network isolation
networks:
- bitwarden-net
Configure /etc/docker/daemon.json:
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
},
"userland-proxy": false,
"no-new-privileges": true,
"live-restore": true,
"userns-remap": "default"
}
Enable user namespace remapping:
# Edit /etc/subuid and /etc/subgid
sudo echo "dockremap:$(id -u):1" >> /etc/subuid
sudo echo "dockremap:$(id -g):1" >> /etc/subgid
# Restart Docker
sudo systemctl restart docker
Create isolated Docker network:
networks:
bitwarden-net:
driver: bridge
internal: false # false to allow external access via proxy
ipam:
config:
- subnet: 172.28.0.0/16
gateway: 172.28.0.1
Bind to localhost only (when using reverse proxy):
ports:
- "127.0.0.1:8080:8080" # Only accessible from localhost
# Install Certbot
sudo apt-get install -y certbot python3-certbot-nginx
# Obtain certificate
sudo certbot --nginx -d bitwarden.example.com
# Auto-renewal (already configured by certbot)
sudo certbot renew --dry-run
Create /etc/nginx/sites-available/bitwarden:
server {
listen 80;
server_name bitwarden.example.com;
# ACME challenge
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
# Redirect to HTTPS
location / {
return 301 https://$server_name$request_uri;
}
}
server {
listen 443 ssl http2;
server_name bitwarden.example.com;
# SSL certificates
ssl_certificate /etc/letsencrypt/live/bitwarden.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/bitwarden.example.com/privkey.pem;
# TLS configuration
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:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off;
# OCSP Stapling
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;
# Security headers
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' wss:; frame-ancestors 'self';" always;
# Rate limiting
limit_req_zone $binary_remote_addr zone=bitwarden_limit:10m rate=10r/s;
limit_req zone=bitwarden_limit burst=20 nodelay;
# Proxy settings
location / {
proxy_pass http://127.0.0.1:8080;
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;
# WebSocket support
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
Submit your domain to HSTS preload list:
# Verify HSTS header
curl -I https://bitwarden.example.com | grep -i strict-transport-security
# Submit at: https://hstspreload.org/
Admin Console Configuration:
Environment Variables:
# Disable weaker 2FA methods
globalSettings__disableEmailToken=true
# Enable WebAuthn/FIDO2
globalSettings__disableWebAuthn=false
# YubiKey configuration
globalSettings__yubico__clientId=your-client-id
globalSettings__yubico__key=your-secret-key
# Minimum length
globalSettings__passwordPolicy__minLength=16
# Complexity requirements
globalSettings__passwordPolicy__requireUpper=true
globalSettings__passwordPolicy__requireLower=true
globalSettings__passwordPolicy__requireNumber=true
globalSettings__passwordPolicy__requireSpecial=true
# Password history
globalSettings__passwordPolicy__preventReuse=10
# Expiration (days)
globalSettings__passwordPolicy__expirationDays=90
Configure SAML or OIDC for enterprise authentication:
SAML with Keycloak:
globalSettings__saml__spEntityId=bitwarden
globalSettings__saml__spMetadataUrl=https://bitwarden.example.com/sso/metadata
globalSettings__saml__spAcsUrl=https://bitwarden.example.com/sso/acs
globalSettings__saml__idpEntityId=https://idp.example.com/realms/myrealm
globalSettings__saml__idpSingleSignOnServiceUrl=https://idp.example.com/realms/myrealm/protocol/saml
globalSettings__saml__idpX509PublicCert=MIIC...
# Rotate API tokens regularly
# Admin Console β Organization β Settings β API
# Revoke unused tokens
# Monitor token usage in audit logs
Bitwarden uses zero-knowledge encryption:
Encrypt backups with GPG:
# Generate GPG key
gpg --full-generate-key
# Encrypt backup
gpg --encrypt --recipient your-email@example.com \
--output bitwarden-backup-$(date +%Y%m%d).tar.gz.gpg \
bitwarden-backup-$(date +%Y%m%d).tar.gz
# Decrypt backup
gpg --decrypt --output bitwarden-backup.tar.gz \
bitwarden-backup.tar.gz.gpg
Installation ID and Key:
# Use secrets manager (e.g., Infisical, Vault)
export BW_INSTALLATION_ID=$(infisical secrets get BW_INSTALLATION_ID)
export BW_INSTALLATION_KEY=$(infisical secrets get BW_INSTALLATION_KEY)
Enable and monitor audit logs:
# Enable event logging
globalSettings__enableEventLogging=true
# Log retention (days)
globalSettings__eventLogging__retentionDays=365
View logs:
# Docker logs
docker compose logs -f bitwarden-lite
# Application logs
tail -f /opt/bitwarden-lite/data/logs/*.log
# Nginx access logs
tail -f /var/log/nginx/bitwarden_access.log
Configure syslog forwarding:
globalSettings__syslog__enabled=true
globalSettings__syslog__destination=udp://siem.example.com:514
globalSettings__syslog__application=bitwarden
globalSettings__syslog__facility=LOCAL0
Monitor for suspicious activity:
# Failed login attempts
grep -i "failed" /var/log/bitwarden/*.log | tail -50
# Unusual IP addresses
awk '{print $1}' /var/log/nginx/bitwarden_access.log | sort | uniq -c | sort -rn | head -20
# After-hours access
grep "$(date +%Y-%m-%d)" /var/log/nginx/bitwarden_access.log | awk '{print $4}' | cut -d: -f2 | sort | uniq -c
Create monitoring script /usr/local/bin/bitwarden-monitor.sh:
#!/bin/bash
# Check service health
if ! curl -sf http://127.0.0.1:8080/health > /dev/null; then
echo "CRITICAL: Bitwarden health check failed" | mail -s "Bitwarden Alert" admin@example.com
fi
# Check failed logins (last hour)
FAILED_LOGINS=$(grep -c "Failed login" /var/log/bitwarden/*.log 2>/dev/null || echo 0)
if [ "$FAILED_LOGINS" -gt 50 ]; then
echo "WARNING: $FAILED_LOGINS failed login attempts detected" | mail -s "Bitwarden Security Alert" admin@example.com
fi
# Check disk space
DISK_USAGE=$(df /opt/bitwarden | tail -1 | awk '{print $5}' | sed 's/%//')
if [ "$DISK_USAGE" -gt 85 ]; then
echo "WARNING: Disk usage at ${DISK_USAGE}%" | mail -s "Bitwarden Disk Alert" admin@example.com
fi
Add to crontab:
*/15 * * * * /usr/local/bin/bitwarden-monitor.sh
Bitwarden supports SOC 2 compliance:
For healthcare deployments:
For EU data protection:
# Force password reset for all users
# Admin Console β Organization β Users β Select All β Require Password Reset
# Revoke all active sessions
# Admin Console β Organization β Settings β Revoke All Sessions
# Rotate Installation ID and Key
# Update environment variables and restart
Any questions?
Feel free to contact us. Find all contact information on our contact page.