···11+---
22+title: Migrating from SWAG/NGIX to Traefik
33+description: >-
44+ Moving multi-host external/internal services, SSL, cloudflare workers, and crowdsec to Traefik without Swarm
55+author: FoxxMD
66+date: 2025-04-07 10:00:00 -0400
77+categories: [Tutorial]
88+tags: [nginx, docker, traefik, crowdsec, ssl, dns]
99+pin: false
1010+image:
1111+ path: /assets/img/traefik/traefik-dashboard.webp
1212+ alt: Traefik dashboard
1313+---
1414+1515+## Background
1616+1717+### Why?
1818+1919+* NGINX designed for single server topology
2020+ * No service discovery
2121+ * Middleware is hard
2222+* Relying on SWAG doesn't feel first class
2323+ * ENVs for cert generation are unwieldy
2424+ * Still kind of a hack, wish it was 1st party
2525+ * crowdsec mod encourages tight coupling with SWAG scripts
2626+ * cloudflare tunnels encourages tight coupling with SWAG scripts
2727+ * service discovery mod designed for single host
2828+ * my fork works but feelsbadman.jpg
2929+* No birdseye
3030+ * No dashboard or metrics
3131+ * Diagnosing config errors is hard
3232+ * Should not need https://github.com/dvershinin/gixy to do this
3333+3434+### Requirements/Spec
3535+3636+* Must be able to host services with web routing parity WRT NGINX configs
3737+* Cert management must be easier than SWAG
3838+ * Must be able to validate via dns challenge
3939+ * Must be able to validate multiple domains with wildcard subdomains
4040+* Must be able to integrate crowdsec
4141+* Must be able to integrate with cloudflare tunnels
4242+* Must be able to integrate with authentik
4343+* Service discovery must be easier
4444+ * Must be feasible without Swarm/changing current docker topology (5+ hosts)
4545+* Must be able to separate internal/external services
4646+4747+### Evaluating Other Solutions
4848+4949+* [Caddy](https://caddyserver.com/)
5050+ * Requires third party module just for docker discovery https://github.com/lucaslorentz/caddy-docker-proxy
5151+ * [Plugins require custom builds](https://github.com/serfriz/caddy-custom-builds) including cloudflare/crowdsec
5252+* [GoDoxy](https://github.com/yusing/godoxy)
5353+ * Promising but too new
5454+5555+## Satifying Requirements with Traefik
5656+5757+Overviews of how each [Requirement](#requirementsspec) was met
5858+5959+### Web Routing Parity
6060+6161+The hardest requirement to meet. Unlike NGINX,
6262+6363+* chaining together multiple transformations of a route is not as straightforward
6464+* Doing anything that short-circuits a route and isn't already a middleware isn't well documented or designed for
6565+* Traefik doesn't have as many middlewares (plugins) and well-documented, non-trivial "how to do X" examples
6666+6767+Majority are straightforward, examples of SWAG default configs vs traefik labels
6868+6969+#### Chaining Middleware with Non-Trivial Examples
7070+7171+Mastodon example
7272+7373+#### Redirect on non-existent Route
7474+7575+Return 302 instead of 404 for wildcare routes.
7676+7777+#### Missing How Do To X Example
7878+7979+* Minio for mastodon, `customresponseheaders`
8080+* `ignorecert` and `insecureSkipVerify` usage
8181+8282+### Cert Management
8383+8484+Easy. Traefik has built in management.
8585+8686+`websecure.http.tls.certResolver` and `domains.main domains.sans`
8787+8888+https://doc.traefik.io/traefik/https/acme/#providers
8989+https://go-acme.github.io/lego/dns/cloudflare/
9090+9191+### Crowdsec Integration
9292+9393+https://doc.traefik.io/traefik/providers/file/#go-templating
9494+https://masterminds.github.io/sprig/ --> https://masterminds.github.io/sprig/os.html
9595+9696+https://github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin
9797+9898+#### Access Logs
9999+100100+Using custom json format to prevent buffering, drop headers, and keep user agent
101101+102102+```yaml
103103+accessLog:
104104+ filePath: "/var/log/traefik/access.log"
105105+ format: json
106106+ # filters:
107107+ # statusCodes:
108108+ # - "200-299" # log successful http requests
109109+ # - "400-599" # log failed http requests
110110+ # collect logs as in-memory buffer before writing into log file
111111+ bufferingSize: 0
112112+ fields:
113113+ headers:
114114+ defaultMode: drop # drop all headers per default
115115+ names:
116116+ User-Agent: keep # log user agent strings
117117+```
118118+119119+Using [vegardit/docker-traefik-logrotate](https://github.com/vegardit/docker-traefik-logrotate) to keep log files manageable.
120120+121121+A simple alpine container tails logs to stdout (docker logs) and crowdsec ingests this via a docker-socket-proxy connection.
122122+123123+124124+```yaml
125125+source: docker
126126+container_name:
127127+ - traefik-external-traefik-access-logs-1
128128+docker_host: tcp://192.168.CONTAINER.IP:2375
129129+labels:
130130+ type: traefik
131131+ ```
132132+{: file="acquis.yaml" }
133133+134134+### Cloudflare Tunnels Integration
135135+136136+https://github.com/PseudoResonance/cloudflarewarp Real IP
137137+138138+cf tunnel container using config from dashboard, host is traefik container name
139139+140140+### Authentik Integration
141141+142142+https://docs.goauthentik.io/docs/add-secure-apps/providers/proxy/server_traefik
143143+144144+### Service Discovery
145145+146146+Not using Swarm yet so discovery is done using a stack with [traefik-kop](https://github.com/jittering/traefik-kop) and docker-socket-proxy.
147147+148148+#### Separating Interal/External Services
149149+150150+Will eventually by done with [Swarm using `constraints`](https://doc.traefik.io/traefik/providers/swarm/#constraints) but traefik-kop has equivalent functionality using [label `namespace`](https://github.com/jittering/traefik-kop?tab=readme-ov-file#namespaces)
151151+152152+## Additional Traefik Functionality
153153+154154+### Viewing Realtime Logs
155155+156156+Using Logdy with [access logs json file](#access-logs)
157157+158158+### Metrics and Dashboard
159159+160160+Internal dashboards for troubleshooting errors and checking status
161161+162162+Metrics exported in prometheus format and collected into [Traefik Official Standalone Dashboard](https://grafana.com/grafana/dashboards/17346-traefik-official-standalone-dashboard/) with modifications for entrypoint/alias.
163163+164164+```yaml
165165+api:
166166+ dashboard: true
167167+ insecure: true
168168+metrics:
169169+ prometheus:
170170+ buckets:
171171+ - 0.1
172172+ - 0.3
173173+ - 1.2
174174+ - 5.0
175175+```
176176+{: file="static_config/traefik.yaml" }
177177+178178+## Traefik Gotchas
179179+180180+### Ambiguous Config Sources and Documentation
181181+182182+* Difficult to easily grok what the difference between static/dynamic config is
183183+* Difficult to determine where static/dynamic config should be placed in dir/files
184184+* Not obvious that config from separate sources can be used anywhere (file middleware in labels, docker plugin config in files)
185185+ * Parsed config sources not listed anywhere by name
186186+187187+### Finding Config Errors
188188+189189+* Dynamic config issues immediate feedback, visible in dashboard
190190+* Static config does not appear until restart and traefik will bulldoze over silently
191191+ * Errors only show in docker logs but traefik will startup anyways
192192+ * May cause dashboard to entirely disappear if error affects internal routes
193193+ * Errors may look like dynamic if the error trickles downstream
194194+* So..always check docker logs **first** and always restart traefik after any static config changes
195195+196196+### Clobbering Labels
197197+198198+* Copy-pasting labels, may forget service/router name is same as existing
199199+* Traefik does not complain about this unless it causes actually issues (router issue) but will still cause reachability issues while running
200200+201201+### Multiple Services, Same Container
202202+203203+Not an issue but not well documented
204204+205205+* Traefik will complain if multiple routers on container labels but only one service
206206+* Can set service for router on label explicitly
207207+ * Docs examples focus on service name being implicitly define
208208+Ex
209209+210210+```yaml
211211+# ...
212212+ labels:
213213+ traefik.http.routers.mastodon.service: mastodon
214214+ #...
215215+ traefik.http.routers.mastodon-root.service: mastodon
216216+ # ...
217217+ traefik.http.services.mastodon.loadbalancer.server.port: 443
218218+```
219219+220220+## Full Examples
221221+222222+```yaml
223223+here be stacks
224224+```