SSL/TLS Setup
Serve the ScanSuite console over a custom certificate, or behind a web server the host already runs.
A new installation serves the console with a self-signed certificate, so browsers warn. Replace it with a certificate for your domain in one of two ways:
| Route | When |
|---|---|
| Your certificate on ScanSuite's own nginx | The usual case: ScanSuite has ports 80 and 443 to itself. |
| Behind a web server the host already runs | The host's nginx, Apache or a load balancer already serves ports 80 and 443, and keeps the certificate. |
DefectDojo is a separate installation with its own domain and certificate — see Set up DefectDojo.
The values this page uses:
| Placeholder | Where it comes from |
|---|---|
| <YOUR_DOMAIN> | The name the console is served on, such as scansuite.example.com. Your DNS administrator creates it. |
| <SERVER_IP> | The server's address that <YOUR_DOMAIN> points at. |
| <ACCOUNT> | The account that installed ScanSuite and runs ./scansuite. |
| <YOUR_EMAIL> | For Let's Encrypt only: where expiry notices go. |
Your certificate on ScanSuite's nginx
1. Point a DNS name at the server
Create a DNS A record for <YOUR_DOMAIN> pointing at <SERVER_IP>, in your DNS provider or with your DNS administrator.
dig +short <YOUR_DOMAIN>Expected: <SERVER_IP>. A new record can take a while to appear.
2. Get a certificate
You need the certificate with its full chain and its private key, both in PEM format. Take one of these:
From your own certificate authority
Request a certificate for <YOUR_DOMAIN> from your organisation's certificate authority and copy the certificate chain and the private key to the server, for example to ~/certs/fullchain.pem and ~/certs/privkey.pem. Then go to step 3.
From Let's Encrypt
For a server the internet reaches on port 80. Certbot borrows port 80 for a minute, so the command below stops ScanSuite's nginx while it runs, copies the certificate into place and starts nginx again — on the first run and at every automatic renewal. Scans keep running; the console is unreachable for that minute.
sudo snap install certbot --classicThe --agree-tos option accepts the Let's Encrypt subscriber agreement on your behalf. A person reads it and decides to run the command.
sudo certbot certonly --standalone -d <YOUR_DOMAIN> -m <YOUR_EMAIL> --agree-tos \
--pre-hook 'cd /opt/scansuite && docker compose stop nginx' \
--deploy-hook 'install -o <ACCOUNT> -m 644 "$RENEWED_LINEAGE/fullchain.pem" /opt/scansuite/services/nginx/certs/cert.pem && install -o <ACCOUNT> -m 600 "$RENEWED_LINEAGE/privkey.pem" /opt/scansuite/services/nginx/certs/key.pem' \
--post-hook 'cd /opt/scansuite && docker compose start nginx'Expected: Successfully received certificate. The certificate is already in place; go to step 4. Check that renewal will work — this stops and starts nginx too:
sudo certbot renew --dry-runExpected: Congratulations, all simulated renewals succeeded.
3. Put the certificate in place
For a certificate from your own CA. nginx expects the certificate with its full chain as cert.pem and the private key as key.pem. Both must belong to your account, or the next ./scansuite update stops with cannot read …:
cd /opt/scansuite
cp ~/certs/fullchain.pem services/nginx/certs/cert.pem
cp ~/certs/privkey.pem services/nginx/certs/key.pem
chmod 600 services/nginx/certs/key.pem
docker compose restart nginxdocker compose restart nginx reloads only nginx, so scans keep running (prefix sudo if your account is not in the docker group). If you copied the files with sudo, make them yours first: sudo chown "$USER": services/nginx/certs/cert.pem services/nginx/certs/key.pem.
4. Check the certificate
cd /opt/scansuite && ./scansuite doctor
openssl s_client -connect <YOUR_DOMAIN>:443 -servername <YOUR_DOMAIN> </dev/null 2>/dev/null | openssl x509 -noout -subject -enddateExpected: doctor reports TLS certificate valid for N more days and ends with No problems found; openssl prints your domain as the subject and the expiry date; the browser opens https://<YOUR_DOMAIN> without a warning.
If doctor reports services/nginx/certs/key.pem is not the private key of cert.pem, the two files do not belong together and nginx will not start with them: copy the matching key. Updates keep your certificate and key as a pair. If they do not match at an update, it puts the release's self-signed pair in place, so the console still answers, and keeps yours in a .replaced-<date> directory in /opt/scansuite.
Behind a web server the host already runs
When the host already serves ports 80 and 443 — its own nginx or Apache, or a proxy in front of several sites — keep the certificate there and let that server forward to ScanSuite.
1. Hand the ports to the host
Publish the console (the web service, port 5000) on the loopback address and switch off ScanSuite's own nginx in /opt/scansuite/docker-compose.local.yml:
services:
web:
ports:
- "127.0.0.1:5000:5000"
nginx:
profiles: ["bundled-nginx"]The file belongs to the host: updates never change it, and install, update and start always include it. Apply it and check:
cd /opt/scansuite && ./scansuite restart
./scansuite doctor
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:5000/log_inExpected: doctor reports ports 80 and 443 are left to the host: this installation runs no nginx, and curl prints 200.
2. Point the host's web server at it
For nginx:
server {
listen 443 ssl;
server_name <YOUR_DOMAIN>;
ssl_certificate /etc/letsencrypt/live/<YOUR_DOMAIN>/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/<YOUR_DOMAIN>/privkey.pem;
client_max_body_size 2000M;
proxy_read_timeout 600;
proxy_connect_timeout 600;
proxy_send_timeout 600;
location / {
proxy_pass http://127.0.0.1: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;
}
}
server {
listen 80;
server_name <YOUR_DOMAIN>;
return 301 https://$host$request_uri;
}Keep the body size and the timeouts, which are the ones ScanSuite's own nginx uses: code archives and reports are uploaded through the console, and without them the proxy cuts large uploads and long requests short with 413 and 504 errors.
Check: curl -sI https://<YOUR_DOMAIN>/log_in | head -1 prints a 200 status line.
3. Tell ScanSuite how many proxies stand in front of it
ScanSuite reads the visitor's address — for rate limits and the sign-in log — from what the last proxy forwards, and trusts only as many proxies as TRUSTED_PROXY_HOPS in /opt/scansuite/.env says:
| In front of the console | TRUSTED_PROXY_HOPS |
|---|---|
| ScanSuite's own nginx, nothing in front | 1 (the default) |
| The host's web server in place of ScanSuite's nginx, as above | 1 (the default) |
| A load balancer or another proxy in front of either | 2 |
Too low, and every visitor looks like the proxy and shares one rate limit; too high, and a visitor can forge their address. After changing it, run ./scansuite restart.
| .env setting | What it does | Default | When to change |
|---|---|---|---|
| TRUSTED_PROXY_HOPS | How many proxies in front of the console are trusted to report the visitor's address. | 1 | A load balancer in front: 2. |
| MAX_UPLOAD_MB | The largest upload the console accepts, in MB. | 2000 | Larger code archives: raise it, and client_max_body_size in the proxy with it (and in services/nginx/default.conf.template for ScanSuite's own nginx). |
Last reviewed 2026-10-19