All user-specific values live in config.env at the repo root. Fill it in once before running anything. The scripts auto-discover it and fall back to per-variable defaults if it is not found.
For the web-server profile, a second file — config.web.env — holds web-layer settings. Copy it to the repo root alongside config.env before running bootstrap with --profile web-server.
The port SSH listens on. Default: 22.
Update this if you run SSH on a non-standard port. The value propagates automatically to the UFW rule and the fail2ban jail created by 01-immediate-hardening.sh.
SSH_PORT=2222The username to set up as a sudo admin in core/02-setup-admin-user.sh. No default — the script aborts if this is unset.
The user must already exist on the server before running script 03. Create it first if needed:
adduser youruserADMIN_USER="youruser"Where to send reports and alerts. Used by core/03-monthly-updates-setup.sh (monthly upgrade report) and web/02-log-monitoring-setup.sh (Logwatch digest). No default — scripts abort if unset.
ADMIN_EMAIL="you@example.com"The From address on outgoing server emails. Used by web/02-log-monitoring-setup.sh for the Logwatch configuration. Defaults to server@<hostname> if not set.
MAIL_FROM="server@yourdomain.com"SMTP relay host and port for msmtp. Used by core/03-monthly-updates-setup.sh.
Common values:
| Provider | Host | Port |
|---|---|---|
| Gmail | smtp.gmail.com |
587 |
| Postmark | smtp.postmarkapp.com |
587 |
| Mailgun | smtp.mailgun.org |
587 |
| SendGrid | smtp.sendgrid.net |
587 |
SMTP_HOST="smtp.gmail.com"
SMTP_PORT=587SMTP authentication credentials. Leave empty to attempt unauthenticated relay, which rarely works from VPS providers.
For Gmail, use an App Password rather than your account password.
SMTP_USER="you@gmail.com"
SMTP_PASS="your-app-password"config.web.env is the web-server counterpart to config.env. It is loaded automatically by all scripts under scripts/web/ and is also sourced by bootstrap.sh when running the web-server profile. Copy it to the repo root alongside config.env and fill in your values. If it is absent, all web scripts fall back to safe per-variable defaults.
Controls which domains may embed your pages in iframes. Used by web/01-apache-hardening.sh to set the Content-Security-Policy header's frame-ancestors directive.
Common configurations:
# Block all embedding
CSP_FRAME_ANCESTORS="'none'"
# Same-origin only
CSP_FRAME_ANCESTORS="'self'"
# Same-origin + specific trusted domains
CSP_FRAME_ANCESTORS="'self' yourdomain.com www.yourdomain.com app.yourdomain.com"Days before TLS certificate expiry at which web/03-cert-monitor-setup.sh sends a warning email. Default: 30.
CERT_WARN_DAYS=30The directory that contains your virtual host document roots. Used by web/06-vhost-hardener.sh and the web/audit/web-root-perms.sh checker. Defaults to /var/www if not set.
WEB_ROOTS_DIR="/var/www"By default, bootstrap.sh shows a single confirmation prompt before running any scripts:
Type AGREE to continue or Ctrl+C to abort:
Type AGREE and press Enter to proceed. This prompt fires once per bootstrap run — individual sub-scripts do not prompt again when called from bootstrap.
Skip the prompt with --confirm:
bash bootstrap.sh --confirm
bash bootstrap.sh --confirm --profile baselineUse --confirm for automation, CI pipelines, or repeat runs where the prompt is unwanted.
Behavior summary:
| Invocation | Prompts |
|---|---|
bash bootstrap.sh |
Once, at the top of bootstrap |
bash bootstrap.sh --confirm |
Never — skipped entirely |
bash bootstrap.sh --dry-run |
Never — dry-run implies no changes |
bash scripts/core/hardening/01-immediate-hardening.sh |
Once, in that script |
bash scripts/core/hardening/01-immediate-hardening.sh --confirm |
Never |
When running scripts individually, each prompts independently. The single-prompt guarantee only applies when running through bootstrap.sh.
Every hardening script and bootstrap.sh supports --dry-run:
bash bootstrap.sh --dry-runThis prints every change that would be made without touching the system. Run it after editing config.env to verify the values look correct before committing to a live run.
After running core/03-monthly-updates-setup.sh and web/02-log-monitoring-setup.sh, verify email delivery manually:
/usr/local/sbin/monthly-apt-report.sh
/usr/local/sbin/goaccess-daily-report.shIf mail does not arrive, check /var/log/msmtp.log and verify your SMTP credentials and relay configuration.
When managing multiple servers, each server may need a slightly different configuration (different email, different SSH port, or different SMTP relay). The recommended pattern is to use config.env as the shared base and a server-specific config.env.local for overrides.
private/
servers/
server1.example.com/
config.env.local # server-specific overrides
server2.example.com/
config.env.local
Export CONFIG_FILE to point at your base config, then source the override file on top:
export CONFIG_FILE=/etc/linux-security/config.env
source /etc/linux-security/config.env.local # optional override
bash bootstrap.shOr combine them in your deployment workflow:
# Merge base + override into a single file for the target server
cat config.env private/servers/server2.example.com/config.env.local \
> /tmp/server2-config.env
ssh root@server2 "CONFIG_FILE=/tmp/server2-config.env bash -s" < bootstrap.sh# server2.example.com overrides
SSH_PORT=2222
ADMIN_EMAIL=ops-server2@example.com
SMTP_HOST=smtp.mailgun.org
SMTP_PORT=587
SMTP_USER=postmaster@mg.example.com
SMTP_PASS=your-mailgun-api-keyThe private submodule is the right home for these files — they contain real server details and credentials that must not be in the public repo.
The hardening scripts are developed and tested on Ubuntu 24.04 LTS. Most steps are compatible with Debian 12, with the following differences:
| Area | Ubuntu 24.04 | Debian 12 |
|---|---|---|
| Certbot | snap install certbot |
apt install certbot |
| SSH service name | ssh |
ssh (same) |
| fail2ban filter paths | /var/log/auth.log |
/var/log/auth.log (same) |
| AppArmor | Pre-installed | Pre-installed (may differ in profile set) |
| snap packages | Available | Not available by default |
If running on Debian 12, install certbot via apt rather than snap:
apt install certbot python3-certbot-apacheAll scripts auto-detect the certbot path — they check command -v certbot first, then fall back to /snap/bin/certbot only if it exists. No manual path changes are needed on Debian 12.
All other scripts run without modification on Debian 12. Testing on Debian 12 is tracked in issue #100.