How to Use SWAG: The Beginner's Guide (First Compose, the CERT-EMAIL Lesson, Proxy Confs, and the Fail2ban Bonus)
CodexFuse Team · 2026-11-01T17:20:39.845Z · 10 min read
SWAG is LinuxServer's reverse proxy container: nginx + certbot + fail2ban in one box. This beginner guide covers your first compose file, certs, proxy confs, subdomains, basic auth, and fail2ban.
So you have a few services running on your server — maybe a media server, a file-sync app, a home dashboard — and you want to reach them from anywhere with proper HTTPS, without buying certificates or hand-configuring nginx. Learning how to use SWAG is the shortest path there. This LinuxServer SWAG tutorial walks you through the whole arc: your first compose file, the cert-email lesson, proxy confs for subdomains and subfolders, basic auth, and the fail2ban bonus that ships for free. If you are new to containers, read our Docker beginner guide first — SWAG assumes you can write a compose file, and everything below builds on it.
What Is SWAG, Exactly?
SWAG — Secure Web Application Gateway — is a single Docker container that acts as the front door for your homelab: it terminates TLS with automatically issued and renewed certificates, reverse-proxies each service to its own subdomain or subfolder, and ships fail2ban to block brute-force attempts. The mental model is one sentence: one public port 443, certificates that renew themselves, and every service gets its own address. Under the hood it is nginx plus a certbot client plus fail2ban, pre-wired so you configure one container instead of three services. Say it once and the rest of the guide makes sense: SWAG = nginx + automatic certs + a bouncer, in one box.
What Do You Need Before SWAG?
You need four things, and all four are cheap. First, a server you control — our guide on what is VPS hosting explains the hosting side if you are starting from zero. Second, a domain you control; a free DuckDNS subdomain works fine. Third, Docker and Docker Compose installed. Fourth, the ability to forward ports on your router, because SWAG's certificate validation needs to be reachable from the internet.
Your First SWAG Docker Compose File
A working SWAG docker compose setup takes about five minutes once the ports and DNS are ready. Here is the full arc:
- Create a folder, for example
swag, and inside it acompose.yamlfile. - Paste the compose file below, replacing
example.comwith your domain. - Forward ports 80 and 443 on your router to the machine running Docker.
- Run
docker compose up -dand watch the logs withdocker logs -f swaguntil you see the certificate issued. - Open
https://example.comin a browser — you should get SWAG's welcome page over a valid certificate.
services:
swag:
image: lscr.io/linuxserver/swag:latest
container_name: swag
cap_add:
- NET_ADMIN
environment:
- PUID=1000
- PGID=1000
- TZ=Etc/UTC
- URL=example.com
- SUBDOMAINS=www,cloud
- VALIDATION=http
- [email protected]
- STAGING=true
volumes:
- ./config:/config
ports:
- 443:443
- 80:80
restart: unless-stopped
Three notes on that file, each worth one read. The image is lscr.io/linuxserver/swag — that is the pull name the current official docs use. If an old tutorial tells you to pull linuxserver/letsencrypt, know that name is retired: SWAG is the renamed successor, and old guides predate the rename, so follow the new name. cap_add: NET_ADMIN is required so fail2ban can modify iptables rules. And STAGING=true is deliberate: your first run should hit the staging servers so a mistake does not burn you into a rate limit — flip it to false and recreate the container once everything works.
The CERT-EMAIL Lesson: EMAIL, CERTPROVIDER, and STAGING
Three env vars control your certificates, and beginners get tripped by all three. URL is the top domain you control; SUBDOMAINS is the comma-separated list the cert should cover, or exactly wildcard for a wildcard cert; VALIDATION is http or dns. The CERT-EMAIL lesson is the part people skip: EMAIL is optional for Let's Encrypt — it is used for cert expiration notifications — but it is required if you set CERTPROVIDER=zerossl, because ZeroSSL ties certificates to an account. SWAG defaults to Let's Encrypt; ZeroSSL has no rate limits and a web UI for cert management, at the cost of needing that account. And STAGING=true uses the ACME staging servers with much higher rate limits, so the official advice is to prove your setup on staging first, then go live. Certificates are valid for 90 days; the container checks them twice daily using ACME Renewal Information to pick the optimal renewal window, and renewal logs live at /config/log/letsencrypt.
Port 80 and 443: The Only Rule You Need
Port 443 is always required — that is the port your browser uses for HTTPS. Port 80 forwarding is required only when VALIDATION=http, because the ACME challenge reaches the container over port 80. If you use dns validation instead (with DNSPLUGIN and credentials in /config/dns-conf/), port 80 can stay closed. One legacy warning: older community guides mention a DHLEVEL variable for Diffie-Hellman key length — it does not exist in the current official docs at all, so ignore any guide that tells you to set it.
Proxy Confs: The .sample Ritual
SWAG proxy conf files are preset nginx configs for hundreds of popular apps, and enabling one is a ritual: samples live at /config/nginx/proxy-confs/ as inactive .sample files; you rename one to drop the .sample (for example nextcloud.subdomain.conf.sample becomes nextcloud.subdomain.conf) and restart the container. Nginx loads any file matching *.subdomain.conf or *.subfolder.conf at startup, so the rename is the entire activation step. Two rules matter: the proxied app must be on the same user-defined bridge network as SWAG so it resolves by container name, and any prerequisites are written at the top of each sample file — read them before renaming.
| SWAG subdomain conf | SWAG subfolder conf | |
|---|---|---|
| Address style | app.example.com | example.com/app |
| Cert coverage | Each subdomain needs SAN or wildcard cert | Covered by the main domain cert |
| App compatibility | Works with nearly every app | Some apps refuse to run under a path prefix |
| Beginner verdict | Start here — fewer surprises | Use when an app breaks as a subdomain |
The update rule is the part that bites later: proxy sample files get updated by new image releases, but your renamed, enabled confs are never touched — compare them against the new samples manually when you update. And if renaming confs one by one feels slow, the auto-proxy mod (DOCKER_MODS=linuxserver/mods:swag-auto-proxy) discovers local services and generates the nginx server blocks automatically.
Locking Doors With htpasswd Basic Auth
Some apps have no login of their own, so SWAG lets you gate them at the proxy. The exact official command creates the password file: docker exec -it swag htpasswd -c /config/nginx/.htpasswd username — and the -c flag is for the first user only, because it deletes any existing file; additional users run the same command without -c. Then uncomment the auth_basic lines in that app's conf and restart. If you want real single sign-on at the proxy instead, SWAG supports forward-auth to Authelia or Authentik. A SWAG Authelia pairing means Authelia handles the login while SWAG enforces it at the proxy — a separate identity service doing the login and SWAG asking it "is this visitor allowed". One mention, because basic auth covers most beginners and SSO is the graduation step.
Fail2ban: The Free Bonus
SWAG fail2ban integration is the feature people discover by accident: the container ships fail2ban pre-configured and enabled, with five default jails watching for bad behavior out of the box. That is why cap_add: NET_ADMIN matters — fail2ban writes iptables rules to drop attacking IPs, and it cannot do that without the capability. Here are the five jails you get for free:
| Jail | What it watches |
|---|---|
nginx-http-auth | Repeated failed basic-auth logins |
nginx-badbots | Known malicious bot user-agents |
nginx-botsearch | Scans for common exploit paths (wp-admin, phpmyadmin, and friends) |
nginx-deny | Requests denied by nginx rules |
nginx-unauthorized | 401/403 responses piling up from one IP |
If you ever lock yourself out, the unban command is docker exec swag fail2ban-client unban <ip address>. Enable or disable other jails in /config/fail2ban/jail.local (never edit the .conf files — they get overwritten), and set DISABLE_F2B=true if you ever want fail2ban off entirely.
SWAG vs Caddy vs Pangolin: Draw the Lines
Three tools solve "expose my services safely" and beginners mix them up, so draw the lines once. Caddy is automatic everything in one file: you write a five-line Caddyfile and certificates, redirects, and the reverse proxy just happen — it is the simplicity champion, and our Caddy beginner guide covers it. SWAG is nginx power with preset proxy confs plus fail2ban in one container: more knobs, more app-specific presets, a built-in bouncer. Pangolin is the zero-trust tunnel lane: it exposes services without any public ports at all, which is a fundamentally different architecture — no 443 on your router, traffic flows through a tunnel instead. Choose Caddy for the shortest path, a SWAG reverse proxy when you want nginx's ecosystem and per-app presets, Pangolin when opening ports is not an option — our Pangolin beginner guide covers that tunnel lane in full.
| SWAG | Caddy | Pangolin | |
|---|---|---|---|
| Architecture | Public 443 reverse proxy | Public 443 reverse proxy | Zero-trust tunnel, no open ports |
| Config style | nginx confs + presets | One short Caddyfile | Dashboard + tunnels |
| Certs | certbot, Let's Encrypt or ZeroSSL | Automatic, built in | Handled by the tunnel |
| Brute-force defense | fail2ban pre-configured | Add your own | Access control at the tunnel |
| Best for | nginx control + app presets | Fastest simple setup | No public ports allowed |
Keeping SWAG Healthy
SWAG mostly runs itself, but two maintenance habits keep it boring in the good way. Certificates renew on their own — the container checks twice daily and uses ACME Renewal Information to pick the optimal renewal window, so a green padlock that never expires is the expected steady state. Container and config updates are the manual part: SWAG notes config changes in its changelog but never auto-applies them to your files, so after an update, review the changelog, and for any file you never modified, delete it and restart to get the fresh default. For proxy samples specifically, watch the linuxserver/reverse-proxy-confs commit history, since sample updates are not listed in the changelog — and remember your renamed confs are yours forever. If you are exposing services to the open internet, read our WireGuard beginner guide too: keeping admin panels behind a private VPN instead of the public web is the strongest hardening step you can take.
7 Beginner Mistakes With SWAG
| Mistake | What happens | Fix |
|---|---|---|
Forgetting port 80 with http validation | Certificate issuance fails | Forward port 80, or switch to dns validation |
Skipping STAGING=true on the first run | Repeated failures trigger Let's Encrypt rate limits | Test on staging first, then go live |
| Proxied app on the wrong Docker network | 502 Bad Gateway | Put the app on SWAG's user-defined bridge network |
Using -c on htpasswd for a second user | Wipes the existing password file | -c is first-user-only; omit it after |
| Enabling the proxy conf before DNS propagates | Validation fails, cert never issues | Confirm DNS resolves before docker compose up |
Editing fail2ban .conf files | Changes overwritten on update | Override in jail.local and .local filters |
| Expecting renamed proxy confs to auto-update | Stale configs silently fall behind | Diff against new samples after each update |
Frequently Asked Questions
Is SWAG free to use?
Yes. SWAG is an open-source LinuxServer container, and the certificates it issues come from Let's Encrypt or ZeroSSL, both free. The only costs are the ones you already have: a server and a domain.
Do I really need port 80 open?
Only if you use http validation. With dns validation and a DNSPLUGIN, port 80 can stay closed — the ACME challenge happens through DNS records instead.
Can I use Cloudflare DNS with SWAG?
Yes. Set VALIDATION=dns and DNSPLUGIN=cloudflare, then put your API token in /config/dns-conf/cloudflare.ini. Cloudflare's free DNS tier works fine for this.
Should I use SUBDOMAINS=wildcard or list my subdomains?
A wildcard needs dns validation, but then covers any subdomain you invent later. Listing subdomains works with http validation and is simpler to start with — you can switch to a wildcard whenever you outgrow the list.
How do I add a new service after the first run?
Put the service on SWAG's bridge network, rename its proxy conf to drop .sample, and restart the container (or enable the auto-reload mod). The existing certificate already covers the subdomain, so the new service is live in seconds.
Sources
Every fact in this guide was verified against official linuxserver sources fetched on 2026-11-01: the SWAG getting-started guide at docs.linuxserver.io/general/swag, the full README and Parameters table at github.com/linuxserver/docker-swag, the image page at hub.docker.com/r/linuxserver/swag, the hardening walkthrough at linuxserver.io/blog/securing-swag, the zero-trust and auto-proxy examples at linuxserver.io/blog/zero-trust-hosting-and-reverse-proxy-via-cloudflare-swag-and-authelia, and the 2020 rename announcement at linuxserver.io/blog/2020-08-21-introducing-swag.