Istall host without nginx in docker

I am trying to install bitwarden on a Ubuntu 20 server.
I already have nginx and certbot installed handling the http to https.
I am serving several domains with this set up and it works fine. All http traffic is refused and the requesting browser/computer then uses https (if it can).
Doing it this way I can have several separate domains along with individual certificates (handled by certbot)
I can manually install bitwarden (without using the bitwarden.sh)
This works fine BUT the method of providing an admin token has changed. I am now unable to provide a token as the method is now to set an environmental variable email address and a token is sent each time admin access is required
If I use bitwarden.sh to install I can then add the variable as normal but it is installed with another instance of nginx and (yet another set of certificates) I do not want to have to copy certificates from the default letsencrypt location every time they are renewed or docker is rebuilt etc.
Is there anyway to stop the generation of the nginx in the docker?
I believe I could possibly fiddle with the ports in the bitwarden nginx and change the config to effectively bypass the instance of nginx, but that still leaves the problems of the certificates.
At the moment nginx is working as a reverse proxy as it should.
Is bitwarden.sh able to write global environmental variables in the command line?
I could have nginx bypass the bitwarden domain and reverse proxy it as is to the docker nginx server but again the certificates become prolematic. I would rather not have any http bypass the nginx if at all possible
Any suggestions would be much appreciated.

Well after much gnashing of teeth, bouts of unrestrained swearing I worked it out.
Simple really.
1 setup docker
2 setup nginx
(leave the default file alone!)
3 setup test files as follows /var/html/your.first.domain/public_html/index.html
4 setup test files as follows /var/html/your.second.domain/public_html/index.html
5 setup test files as follows /var/html/your.third.domain/public_html/index.html
(each index file should contain some HTML code including the domain name so you can identify it)
You should be able to get to each domain with http (http:// example your.first.domain
6 In the /etc/nginx/sites-available create empty files with the SAME name as your domains (in this case 3 files named your.first.domain your.second.domain your.third.domain)
These files along with the file called default are the configuration files for all the domains that nginx will service.
create a symbolic link from all these files to /etc/nginx/sites-enabled
(example sudo ln -s /etc/nginx/sites-available/your.first.domain /etc/nginx/sites-enabled/your.first.domain)
7 Install certbot and run it. The certbot webpage has superb step by step instructions.
it will interactively create the certificates, set up a cron job to update them as required, and also modify the files you created.
8 sudo nginx -t will make sure everything went right
9 sudo service nginx restart
10 Now when you try to go to a http site nginx will negotiate and change to https (example your.first.domain will now change to https://your.first.domain)
You can just not create the 3 files and certbot will just add the configuration to the default file. (I found it easier to make individual files so that changes to one file did not effect the other domains).

With this setup there can be no connections using http. All must be made with https.
Install bitwarden
Create a test file called env.list
This file contains
[email protected]
ADMIN_TOKEN=somepasswordtoken (use openssl rand -base64 48 to generate a random , but you need to enter it each time you want admin access))

sudo docker run -d --name bitwarden --env-file env.list --restart=always -v /bw-data/:/data/ -p 127.0.0.1:8080:80 -p 127.0.0.1:3012:3012 vaultwarden/server:latest

Now comes the slightly tricky bit
Whichever domain you will use for bitwarden, you need to modify the nginx configuration file you created in /etc/nginx/sites-available
If you were using your.first. domain it should look like this

server {

    server_name your.first.domain;

    root /var/www/your.first.domain/public_html;
    index index.html;

    location / {
            try_files $uri $uri/ =404;
    }

listen [::]:443 ssl; # managed by Certbot
listen 443 ssl; # managed by Certbot
ssl_certificate /etc/letsencrypt/live/your.first.domain/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/your.first.domain/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 = your.first.domain) {
return 301 https://$host$request_uri;
} # managed by Certbot

    listen 80;
    listen [::]:80;

    server_name your.first.domain.com;
return 404; # managed by Certbot

}

You need to add a few things so it looks like this

server {
server_name your.first,domain;
listen [::]:443 ssl ipv6only=on; # managed by Certbot
listen 443 ssl; # managed by Certbot
ssl_certificate /etc/letsencrypt/live/your.first.domain/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/your.first.domain/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_pass http://0.0.0.0: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;
}

location /notifications/hub {
proxy_pass http://0.0.0.0:3012;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection “upgrade”;
}

location /notifications/hub/negotiate {
proxy_pass http://0.0.0.0:8080;
}
}

server {
if ($host = your.first.domain) {
return 301 https://$host$request_uri;
} # managed by Certbot

    listen 80;
    listen [::]:80;

    server_name your.first.domain;
return 404; # managed by Certbot

}
next
sudo nginx -t
if ok then
sudo service nginx restart

You should be there.
In your browser going to http://your.first.domain will result in you going to https://your.first.domain which should go to the Bitwarden login screen
going to your.first.domain/admin should prompt you for the ADMIN_TOKEN then you will be able to enter various email addresses for the bitwarden server to be able to send emails.

It would be helpful if someone with a lot more knowledge than me could suggest changes, particularly in the admin login method.
I think it is different when installed with bitwarden.sh

Bitwarden does not use an Admin Token at all.

Are you trying to install Vaultwarden? This is another product which is based on Bitwarden, but not “official”.

Regarding your Nginx and certificate issues:
I simply installed Bitwarden (the “official” one) with the bitwarden.sh script and disabled its built-in certificate management during installation as described here: Install and Deploy - Linux | Bitwarden Help & Support

Then I changed the Bitwarden ports from 80 and 443 to different ones as described in the documentation and configured my existing Apache as reverse proxy for these custom ports. The Apache reverse proxy always redirects HTTP to HTTPS and uses certificates it gets via Certbot.

1 Like

Thanks for your reply @accolon
I knew it was correct to ask the question here. You are absolutely correct.

Originally I wanted to install the bitwarden.sh one but I realized it was installing an instance of nginx within the docker.
What you described is what I was trying to avoid having that “extra” nginx within the docker (in your case its Apache.

As a matter of interest what ports did you use? Did you have to do anything to your firewall ? I block all ports except what I am specifically using.

One advantage of self hosting is that I can delete it all and start again.

I’m running the full Bitwarden stack via Docker, including the Nginx webserver. It uses almost no resources (especially compared to the MSSQL database), so I see no point in disabling it.

I changed the ports in bwdata/config.yml to 8̶0̶8̶0̶ 4480 and 4443 according to the FAQ:

These two ports are only exposed to localhost and not reachable from the outside.

My Apache then passes all HTTPS requests from the outside to the Bitwarden Nginx like this:

        ProxyPass / http://127.0.0.1:4480/
        ProxyPassReverse / http://127.0.0.1:4480/

Thanks for the reply @accolon
I understood the first bit as to changing the ports in the yml file
Was 8080 just a mistype and should have been 4480
If not can’t quite get where the 4480 came from

Yes, of course you are right. I corrected the wrong port number in my previous answer to avoid confusion.

Apache passes the request to the internally exposed HTTP port of the Bitwarden Nginx server. The port number itself can be chosen freely (as long as it is available), but both ends have to use the same one.

Well i did that but it didn’t help.
So I started from scratch with all new install docker,docker_compose, bitwarden, then nginx.
I was going to see how it actually worked.
The important bits of information are the port settings http 80 , https 443
So I had a look at the nginx config in the docker.
The servers that nginx is configured for are the host set up at installation but on port 8080 for http and port 4443 for https.
These ports don’t change, regardless of what ports are in the config.yml
To me that was rather odd.
When I installed nginx with its default it wouldn’t start because port 80 (and 443?) are in use.
so changing the ports in the config.yml file fixed it.
I came across an example of having multiple nginx all sharing a port. That sort of indicated that the new ngimx and the docker nginx should happily co exist.
So what about the ports?
The ports in the config.yml file are for the certbot instance in bitwarden and NOT the nginx instance.

What I found was that without the nginx, the bitwarden was served on *.example.com host rather than the vault.example.com. That will interfere with any other pages. For example i could get to the vault on vault.example.com as well as www.example.com

I needed “system” nginx to serve static pages and just leave the bitwarden nginx to serve bitwarden domain. (i could probably do that in the docker but that is not desirable).
I needed the bitwarden domain configured in the “system” nginx to stop it being treated as a default or undefined web address.
I did not need to proxy it. Well I probably do so that bitwarden certbot will work but i intend to do that externally as well and change the path in the config.yml to reflect that.

So once the certificates are installed by certbot in bitwarden then certbot no longer needs to run but I guess it runs at start time to check the validity of the certificates. So if an external process (the “system” nginx )is using those ports it will fail and bitwarden server will not start.

Next step will be to install certbot and change the config.yml to point to the location of the certificates.
I could go down the path of doing an install using “existing” certificates but the information is a little ambiguous in the config.yml file as to how to put in the path and I am a bit over the web search trial and error method.
I will properly document it all if and when I get it sorted.

Well I finally “fixed” it all.
BUT i am pretty sure bitwarden is not supposed to work like this.
The problem was I was using Firefox to trace what was happening!
When i started using curl things suddenly made sense. It was NOT port 80 that was exposed it was port 443 still exposed.

I setup bitwarden using letsencrypt. It all worked as installed in the default condition (with no external certbot or nginx)
I copied the various certificates from the /opt/bitwarden/bwdata/letsencrypt/live/this.example.com
to /opt/bitwarden/bwdata/ssl/this.example.com/
(in my case it is 5 files all up with the dhparams file)
(I tried to set them up as symbolic links but the symbolic links for bitwarden wont let me)
Edited the config.yml
https_port: 7443
http_port: 7180
ssl_managed_lets_encrypt: false
paths to the certificate files: /etc/ssl/this.example.com/certificate_name .pem
(the “etc” in this container is a symbolic link > “/opt/bitwarden/bwdata”)

Rebuilt and restarted.

tested using curl https://this.example.com --verbose Got wrong version error
tested using curl http://this.example.com: --verbose this succeeded.
Same from firefox, but a different meaningless error.
But http:// so not secure.
Installed nginx and certbot.
In the default.conf
server{
server_name this.example.com;
location / {
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;
proxy_pass https://localhost:7443;
}

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

}

and another instance with
server {
if ($host = this.example.com) {
return 301 https://$host$request_uri;
} # managed by Certbot

listen 80 ;
server_name this.example.com;
return 404; # managed by Certbot

}
Now I have a secure connection to Bitwarden
I also have secure connections to other subdomains as well.
I do understand that the bitwarden certificates will not auto renew but I am working on how to use symbolic links. (the problem being that the “etc” directory within the container is already mapped to /opt/bitwarden/bwdata)

THIS IS NOT HOW THE DOCUMENTATION SAYS IT SHOULD BE DONE
(I will try and amend the previous post to fix up my bad assumptions :hot_face: )
(cant amend it so just deleted it)

So I sorted this out to “almost” what the documentation describes.
This was after a lot of help from Sugianto at Bitwarden support.
I have disabled ssl completely in bitwarden and changed the 2 ports to 7180 & 7443
I then used the external nginx ( & certbot) to provide the https service. The bitwarden host name is proxied from 80 > 7180.
This unfortunately also allows a http connection directly to bitwarden if the port is specified in the url so I was required to block this incoming port 7180 at the firewall stage.
I am still working on a way to block the incoming port (7180) at nginx but so far all my preliminary machinations so far have blocked it both ways.
The above solution I don’t think is completely satisfactory and certainly my previous “working” solution is a cause for concern in that it is yet another way to access the host.
It is still a work in progress though.

For completeness here is the proxy-pass entry in the external nginx

location / {
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;
proxy_pass http://localhost:7180/;
}
(You can get away with just the proxy_pass entry but then you wont have admin access)

I have given this a bit more thought. Rather than chase whether this setup is in fact reasonably safe security wise, I have decided that I wont rely on my self hosting setup.
At the back of my mind is the suspicion that if I can set this up 2 different ways and one of those ways appears to allow http:// access even though it is to a port that usually handles https://
I don’t think this method should work
When I have more time I will actually test a few things I thought of