Put the portal behind a reverse proxy
An nginx site for the portal, with its TLS certificate, the paths that must reach the portal, and what changes for Cloudflare and Amazon DCV.
- For
- Administrators
- Applies to
- Billet 1.0
- Updated
The portal listens on 127.0.0.1:5150 only. A reverse proxy in front of it holds the TLS certificate, passes on the paths the portal serves, and serves the portal's static files itself.
This page uses nginx. Any proxy works if it does the same things: see What any proxy must do.
1. Get a certificate
With the portal's DNS name pointing at this server and port 80 open, Let's Encrypt's certbot is the simplest way:
sudo apt-get install -y certbot python3-certbot-nginx
sudo certbot certonly --nginx -d portal.example.com
The certificate lands in /etc/letsencrypt/live/portal.example.com/, and certbot renews it by itself.
2. Add the site
Create /etc/nginx/sites-available/billet-portal, replacing portal.example.com throughout:
upstream billet_portal {
server 127.0.0.1:5150;
keepalive 16;
}
server {
listen 443 ssl;
server_name portal.example.com;
ssl_certificate /etc/letsencrypt/live/portal.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/portal.example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
# Nobody outside may claim to be the internal DCV zone.
proxy_set_header X-Beam-Zone-Token "";
# People: sign-in, their desktops, the browser session page.
location /sso/ { proxy_pass http://billet_portal; }
location /portal/ { proxy_pass http://billet_portal; }
location = /portal { proxy_pass http://billet_portal; }
# Machines: hosts and relays check in and enroll here.
location /agent/ { proxy_pass http://billet_portal; }
location = /enroll { proxy_pass http://billet_portal; }
location /enroll/ { proxy_pass http://billet_portal; }
location /i/ { proxy_pass http://billet_portal; }
# Administrators: the console and its API.
location /admin/ { proxy_pass http://billet_portal; }
location /console/ { proxy_pass http://billet_portal; }
location = /console { proxy_pass http://billet_portal; }
# Identity providers: user provisioning.
location /scim/ { proxy_pass http://billet_portal; }
# The deployment's root certificate, which the Billet app fetches.
location = /ca.pem { proxy_pass http://billet_portal; }
location = /_health { proxy_pass http://billet_portal; }
# Files nginx serves itself. The sign-in page does not work without these.
location /static/ {
alias /opt/billet/portal/assets/static/;
add_header Cache-Control "no-cache";
add_header X-Content-Type-Options "nosniff" always;
}
location /downloads/ {
alias /opt/billet/portal/downloads/;
default_type application/octet-stream;
add_header X-Content-Type-Options "nosniff" always;
}
# The DCV endpoints never answer on the public listener.
location /dcv/ { return 404; }
location = / { proxy_pass http://billet_portal; }
location / { return 404; }
}
server {
listen 80;
server_name portal.example.com;
location = /_health { proxy_pass http://billet_portal; }
location / { return 308 https://$host$request_uri; }
}
Enable it and reload:
sudo ln -s /etc/nginx/sites-available/billet-portal /etc/nginx/sites-enabled/
sudo rm -f /etc/nginx/sites-enabled/default
sudo nginx -t && sudo systemctl reload nginx
3. Check it from outside
From another computer:
curl -sS -o /dev/null -w '%{http_code}\n' https://portal.example.com/_health
It prints 200. Then open https://portal.example.com/ in a browser: it shows the sign-in page, styled. An unstyled page means /static/ is not being served.
Do not test with curl -k. It hides the certificate problem this step exists to catch.
What any proxy must do
- Pass the paths above to the portal, and answer 404 for
/dcv/on the public listener. - Serve
/static/from/opt/billet/portal/assets/static/. - Keep the
Hostheader the browser sent. The portal uses it to tell its own name from a tenant's own address. - Send
X-Forwarded-ForandX-Forwarded-Proto: https. The portal binds each session to the address that asked for it, and reads that address fromX-Forwarded-For. - Strip any
X-Beam-Zone-Tokenheader that arrives from outside.
Cloudflare in front
The portal's name can be proxied by Cloudflare. nginx must then restore each visitor's real address from CF-Connecting-IP for Cloudflare's address ranges, using nginx's real_ip module, or every session is bound to a Cloudflare address.
Keep each relay's name DNS-only (not proxied) unless you have followed the relay's own instructions for a proxy. See Install a relay.
Amazon DCV
If you broker Amazon DCV desktops, the gateway and DCV Server reach the portal on a second listener with its own name, which passes only /dcv/t/ to the portal. Keep /dcv/ answering 404 on this site. See Networking: the DCV gateway and the Billet relay.