EDIT: the procedure below does not work - use this one instead.
anjanesh I could not get the linked procedure to work, but I was able to get Bitwarden running on our AlmaLinux 9 servers with the manual deploy procedure: https://bitwarden.com/help/install-on-premise-manual/.
Here are the steps, if you follow them carefully and in the order presented then it should work
- Get a Bitwarden app id and key from https://bitwarden.com/host/.
- Create a new shell user to run Bitwarden. In the remaining steps I'll refer to the shell user as 'bwuser'.
- Create a new Nginx Proxy Port app owned by the new shell user and make a note of the new app's port name assignment. In the remaining steps I'll refer to the app as 'bwapp' and the port as '55555'.
- Add the new app to a site with SSL enabled. Make a note of the site domain, which I'll refer to as "bw.domain.com".
- Log in to SSH as the new shell user and run the following commands:
loginctl enable-linger
cd ~/apps/bw
curl -L https://github.com/bitwarden/server/releases/download/v2026.2.0/docker-stub-US.zip -o docker-stub-US.zip
mkdir bwdata
unzip docker-stub-US.zip -d bwdata
- Edit
bwdata/docker/docker-compose.yml to configure your assigned port from step 3 (replace 55555 with your port):
ports:
- '55555:8080'
- Follow steps 3 through 5 at https://bitwarden.com/help/install-on-premise-manual/
- Run the following commands (replace 'bw' and 'bw.domain.com' with your app name and domain):
cd ~/apps/bw/bwdata/bw.domain.com
openssl req -x509 -newkey rsa:4096 -sha256 -nodes -days 365 \
-keyout ./bwdata/ssl/bitwarden.example.com/private.key \
-out ./bwdata/ssl/bitwarden.example.com/certificate.crt \
-reqexts SAN -extensions SAN \
-config <(cat /usr/lib/ssl/openssl.cnf <(printf '[SAN]\nsubjectAltName=DNS:bw.domain.com\nbasicConstraints=CA:true')) \
-subj "/C=US/ST=New York/L=New York/O=Company Name/OU=Bitwarden/CN=bw.domain.com"
- Perform step 7 from https://bitwarden.com/help/install-on-premise-manual/, using the self-signed certificate option in step 7.3.
- Perform steps 8 through 10 from https://bitwarden.com/help/install-on-premise-manual/. You can get your shell UID and GID by running
id in your SSH session.
- Edit
~/apps/bw/bwdata/nginx/default.conf to change the first server section from this...
server {
listen 8080 default_server;
listen [::]:8080 default_server;
server_name bw.domain.com;
return 301 https://bw.domain.com$request_uri;
}
... to this:
server {
listen 8080 default_server;
listen [::]:8080 default_server;
server_name bw.domain.com;
location / {
proxy_pass http://web:5000/;
include /etc/nginx/security-headers-ssl.conf;
include /etc/nginx/security-headers.conf;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'wasm-unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https://haveibeenpwned.com; child-src 'self' https://*.duosecurity.com https://*.duofederal.com; frame-src 'self' https://*.duosecurity.com https://*.duofederal.com; connect-src 'self' wss://bw.sean.opalstacked.com https://api.pwnedpasswords.com https://api.2fa.directory; object-src 'self' blob:;";
add_header X-Frame-Options SAMEORIGIN;
add_header X-Robots-Tag "noindex, nofollow";
}
location /alive {
return 200 'alive';
add_header Content-Type text/plain;
}
location = /app-id.json {
proxy_pass http://web:5000/app-id.json;
include /etc/nginx/security-headers-ssl.conf;
include /etc/nginx/security-headers.conf;
proxy_hide_header Content-Type;
add_header Content-Type $fido_content_type;
}
location = /duo-connector.html {
proxy_pass http://web:5000/duo-connector.html;
}
location = /webauthn-connector.html {
proxy_pass http://web:5000/webauthn-connector.html;
}
location = /webauthn-fallback-connector.html {
proxy_pass http://web:5000/webauthn-fallback-connector.html;
}
location = /sso-connector.html {
proxy_pass http://web:5000/sso-connector.html;
}
location /attachments/ {
proxy_pass http://attachments:5000/;
}
location /api/ {
proxy_pass http://api:5000/;
}
location /icons/ {
proxy_pass http://icons:5000/;
}
location /notifications/ {
proxy_pass http://notifications:5000/;
}
location /notifications/hub {
proxy_pass http://notifications:5000/hub;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $http_connection;
}
location /notifications/anonymous-hub {
proxy_pass http://notifications:5000/anonymous-hub;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $http_connection;
}
location /events/ {
proxy_pass http://events:5000/;
}
location /sso {
proxy_pass http://sso:5000;
include /etc/nginx/security-headers-ssl.conf;
include /etc/nginx/security-headers.conf;
add_header X-Frame-Options SAMEORIGIN;
}
location /identity {
proxy_pass http://identity:5000;
include /etc/nginx/security-headers-ssl.conf;
include /etc/nginx/security-headers.conf;
add_header X-Frame-Options SAMEORIGIN;
}
location /admin {
proxy_pass http://admin:5000;
include /etc/nginx/security-headers-ssl.conf;
include /etc/nginx/security-headers.conf;
add_header X-Frame-Options SAMEORIGIN;
}
}
- Finally, start the container:
cd ~/apps/bw/bwdata
podman-compose -f ./docker/docker-compose.yml up -d
At this point everything should be up and running on the site you created in step 4 above.