Migration from nginx

Hi everyone,

I know this might sound a bit strange, but I’ve spent the last three days trying to sort this out on my own, and unfortunately, I haven’t been successful. Could anyone help me migrate from Nginx to Traefik?
Background:
I have a script that prepares and deploys Nginx along with a couple of applications. The resulting Nginx config files are:
/etc/nginx/stream-enabled/stream.conf

map $ssl_preread_server_name $sni_name {
    hostnames;
    second_domain    app;
    first_domain          www;
    default                   app;
}
upstream app {
    server 127.0.0.1:8443;
}
upstream www {
    server 127.0.0.1:7443;
}
server {
    proxy_protocol on;
    set_real_ip_from unix:;
    listen          443;
    proxy_pass      $sni_name;
    ssl_preread     on;
}

/etc/nginx/sites-available/80.conf

server {
    listen 80;
    server_name first_domain second_domain;
    return 301 https://$host$request_uri;
}

/etc/nginx/sites-available/first_domain

server {
	server_tokens off;
	server_name first_domain;
	listen 7443 ssl http2 proxy_protocol;
	listen [::]:7443 ssl http2 proxy_protocol;
	index index.html index.htm index.php index.nginx-debian.html;
	root /var/www/html/;
	ssl_protocols TLSv1.2 TLSv1.3;
	ssl_ciphers HIGH:!aNULL:!eNULL:!MD5:!DES:!RC4:!ADH:!SSLv3:!EXP:!PSK:!DSS;
	ssl_certificate /etc/letsencrypt/live/first_domain/fullchain.pem;
	ssl_certificate_key /etc/letsencrypt/live/first_domain/privkey.pem;
	if ($host !~* ^(.+\.)?first_domain$ ){return 444;}
	if ($scheme ~* https) {set $safe 1;}
	if ($ssl_server_name !~* ^(.+\.)?first_domain$ ) {set $safe "${safe}0"; }
	if ($safe = 10){return 444;}
	if ($request_uri ~ "(\"|'|`|~|,|:|--|;|%|\$|&&|\?\?|0x00|0X00|\||\|\{|\}|\[|\]|<|>|\.\.\.|\.\.\/|\/\/\/)"){set $hack 1;}
	error_page 400 401 402 403 500 501 502 503 504 =404 /404;
	proxy_intercept_errors on;
	#Admin Panel
	location /admin/ {
		proxy_redirect off;
		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_pass http://127.0.0.1:35546;
		break;
	}
        location /admin {
		proxy_redirect off;
		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_pass http://127.0.0.1:35546;
		break;
	}
 	#Subscription Path
        location /54Z7DF6cq1Ju {
                if ($hack = 1) {return 404;}
                proxy_redirect off;
                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_pass http://127.0.0.1:54272;
                break;
        }
	location /54Z7DF6cq1Ju/ {
                if ($hack = 1) {return 404;}
                proxy_redirect off;
                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_pass http://127.0.0.1:54272;
                break;
        }
	#Subscription Path
        location /Qlgbn53Y {
                if ($hack = 1) {return 404;}
                proxy_redirect off;
                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_pass http://127.0.0.1:54272;
                break;
        }
	location /Qlgbn53Y/ {
                if ($hack = 1) {return 404;}
                proxy_redirect off;
                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_pass http://127.0.0.1:54272;
                break;
        }
 	#Config Path
	location ~ ^/(?<fwdport>\d+)/(?<fwdpath>.*)$ {
		if ($hack = 1) {return 404;}
		client_max_body_size 0;
		client_body_timeout 1d;
		grpc_read_timeout 1d;
		grpc_socket_keepalive on;
		proxy_read_timeout 1d;
		proxy_http_version 1.1;
		proxy_buffering off;
		proxy_request_buffering off;
		proxy_socket_keepalive on;
		proxy_set_header Upgrade $http_upgrade;
		proxy_set_header Connection "upgrade";
		proxy_set_header Host $host;
		proxy_set_header X-Real-IP $remote_addr;
		proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
		if ($content_type ~* "GRPC") {
			grpc_pass grpc://127.0.0.1:$fwdport$is_args$args;
			break;
		}
		if ($http_upgrade ~* "(WEBSOCKET|WS)") {
			proxy_pass http://127.0.0.1:$fwdport$is_args$args;
			break;
	        }
		if ($request_method ~* ^(PUT|POST|GET)$) {
			proxy_pass http://127.0.0.1:$fwdport$is_args$args;
			break;
		}
	}
	location / { try_files $uri $uri/ =404; }
}

/etc/nginx/sites-available/second_domain

server {
	server_tokens off;
	server_name second_domain;
	listen 9443 ssl http2;
	listen [::]:9443 ssl http2;
	index index.html index.htm index.php index.nginx-debian.html;
	root /var/www/html/;
	ssl_protocols TLSv1.2 TLSv1.3;
	ssl_ciphers HIGH:!aNULL:!eNULL:!MD5:!DES:!RC4:!ADH:!SSLv3:!EXP:!PSK:!DSS;
	ssl_certificate /etc/letsencrypt/live/second_domain/fullchain.pem;
	ssl_certificate_key /etc/letsencrypt/live/second_domain/privkey.pem;
	if ($host !~* ^(.+\.)?second_domain$ ){return 444;}
	if ($scheme ~* https) {set $safe 1;}
	if ($ssl_server_name !~* ^(.+\.)?second_domain$ ) {set $safe "${safe}0"; }
	if ($safe = 10){return 444;}
	if ($request_uri ~ "(\"|'|`|~|,|:|--|;|%|\$|&&|\?\?|0x00|0X00|\||\|\{|\}|\[|\]|<|>|\.\.\.|\.\.\/|\/\/\/)"){set $hack 1;}
	error_page 400 401 402 403 500 501 502 503 504 =404 /404;
	proxy_intercept_errors on;
	#Admin Panel
	location /admin/ {
		proxy_redirect off;
		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_pass http://127.0.0.1:35546;
		break;
	}
        location /admin {
		proxy_redirect off;
		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_pass http://127.0.0.1:35546;
		break;
	}
 	#Subscription
        location /54Z7DF6cq1Ju {
                if ($hack = 1) {return 404;}
                proxy_redirect off;
                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_pass http://127.0.0.1:54272;
                break;
        }
	location /54Z7DF6cq1Ju/ {
                if ($hack = 1) {return 404;}
                proxy_redirect off;
                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_pass http://127.0.0.1:54272;
                break;
        }
	#Subscription
        location /Qlgbn53Y {
                if ($hack = 1) {return 404;}
                proxy_redirect off;
                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_pass http://127.0.0.1:54272;
                break;
        }
	location /Qlgbn53Y/ {
                if ($hack = 1) {return 404;}
                proxy_redirect off;
                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_pass http://127.0.0.1:54272;
                break;
        }
 	#Config Path
	location ~ ^/(?<fwdport>\d+)/(?<fwdpath>.*)$ {
		if ($hack = 1) {return 404;}
		client_max_body_size 0;
		client_body_timeout 1d;
		grpc_read_timeout 1d;
		grpc_socket_keepalive on;
		proxy_read_timeout 1d;
		proxy_http_version 1.1;
		proxy_buffering off;
		proxy_request_buffering off;
		proxy_socket_keepalive on;
		proxy_set_header Upgrade $http_upgrade;
		proxy_set_header Connection "upgrade";
		proxy_set_header Host $host;
		proxy_set_header X-Real-IP $remote_addr;
		proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
		if ($content_type ~* "GRPC") {
			grpc_pass grpc://127.0.0.1:$fwdport$is_args$args;
			break;
		}
		if ($http_upgrade ~* "(WEBSOCKET|WS)") {
			proxy_pass http://127.0.0.1:$fwdport$is_args$args;
			break;
	        }
		if ($request_method ~* ^(PUT|POST|GET)$) {
			proxy_pass http://127.0.0.1:$fwdport$is_args$args;
			break;
		}
	}
	location / { try_files $uri $uri/ =404; }
}

I updated my script to generate the following config files for Traefik:

################################# Traefik Config ########################################################

mkdir -p /etc/traefik/{dynamic,ssl,logs}

cat > /etc/traefik/traefik.yml <<EOF
global:
  checkNewVersion: false
  sendAnonymousUsage: false

log:
  level: INFO
  format: json
  filePath: /var/log/traefik/traefik.log

accessLog:
  filePath: /var/log/traefik/access.log
  bufferingSize: 100
  format: json
  filters:
    statusCodes: ["400-499", "500-599"]

entryPoints:
  web:
    address: :80
    http:
      redirections:
        entryPoint:
          to: websecure
          scheme: https

  websecure:
    address: :443
    http:
      tls: {}

providers:
  docker:
    watch: true
    exposedByDefault: false
  file:
    directory: "/etc/traefik/dynamic"
    watch: true

ping: {}

metrics:
  prometheus: {}

api: {}

certificatesResolvers:
  letsencrypt:
    acme:
      email: email@gmail.com
      storage: /etc/traefik/ssl/acme.json
      httpChallenge:
        entryPoint: web
EOF

cat > /etc/traefik/dynamic/routes.yml <<EOF
http:
  routers:
    traefik-dashboard:
      rule: "Host(\`traefik.first_domain\`)"
      entryPoints:
        - websecure
      service: api@internal
      tls:
        certResolver: letsencrypt

    main-domain:
      entryPoints: ["websecure"]
      rule: "Host(\`${first_domain}\`)"
      service: main-service
      middlewares:
        - security-headers
        - check-host-main
      tls:
        certResolver: letsencrypt

    second-domain:
      entryPoints: ["websecure"]
      rule: "Host(\`${second_domain}\`)"
      service: second-service
      middlewares:
        - security-headers
        - check-host-second
      tls:
        certResolver: letsencrypt

    panel-route:
      rule: "Host(\`${domain}\`) || Host(\`${second_domain}\`) && (PathPrefix(\`/${panel_path}\`) || PathPrefix(\`/${panel_path}/\`))"
      service: panel-service
      entryPoints: ["websecure"]
      tls: {}

    sub-route:
      rule: "Host(\`${domain}\`) || Host(\`${second_domain}\`) && (PathPrefix(\`/${sub_path}\`) || PathPrefix(\`/${sub_path}/\`) || PathPrefix(\`/${json_path}\`) || PathPrefix(\`/${json_path}/\`))"
      service: sub-service
      entryPoints: ["websecure"]
      tls: {}

  services:
    main-service:
      loadBalancer:
        servers:
          - url: https://127.0.0.1:7443
    second-service:
      loadBalancer:
        servers:
          - url: https://127.0.0.1:9443
    panel-service:
      loadBalancer:
        servers:
          - url: http://127.0.0.1:${panel_port}
    sub-service:
      loadBalancer:
        servers:
          - url: http://127.0.0.1:${sub_port}

  middlewares:
    security-headers:
      headers:
        sslRedirect: true
        stsIncludeSubdomains: true
        stsPreload: true
        stsSeconds: 31536000
        customFrameOptionsValue: "SAMEORIGIN"
        contentTypeNosniff: true
        browserXssFilter: true
        forceSTSHeader: true
        sslForceHost: true

    check-host-main:
      IPAllowList:
        sourceRange:
          - 127.0.0.1/32

    check-host-second:
      IPAllowList:
        sourceRange:
          - 127.0.0.1/32

tcp:
  routers:
    sni-router:
      rule: "HostSNI(\`${domain}\`) || HostSNI(\`${second_domain}\`)"
      service: second-service
      tls:
        passthrough: true

  services:
    second-service:
      loadBalancer:
        servers:
          - address: 127.0.0.1:8443
EOF

touch /etc/traefik/ssl/acme.json
chmod 600 /etc/traefik/ssl/acme.json
##################################Install traefik#######################################################
if ! command -v docker &> /dev/null; then
    echo "Installing Docker..."
    curl -fsSL https://get.docker.com -o get-docker.sh
    sudo sh get-docker.sh
    echo "Docker installed successfully."
else
    echo "Docker already installed."
fi

docker network create frontend
docker network create backend

cat > ./docker-compose.yml <<EOF
networks:
  frontend:
    external: true
  backend:
    external: true

services:
  traefik:
    container_name: traefik
    image: traefik:v3.3
    networks:
      - frontend
      - backend
    ports:
      - 80:80
      - 443:443
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - /etc/traefik/traefik.yml:/etc/traefik/traefik.yml:ro
      - /etc/traefik/dynamic:/etc/traefik/dynamic
      - /etc/traefik/ssl:/etc/traefik/ssl
      - /etc/traefik/logs:/var/log/traefik
    security_opt:
      - no-new-privileges:true
    restart: unless-stopped
EOF
echo "Run Traefik in Docker..."
docker compose up -d

generated /etc/traefik/dynamic/routes.yml

http:
  routers:
    traefik-dashboard:
      rule: "Host(`traefik.domain_name`)"
      entryPoints:
        - websecure
      service: api@internal
      tls:
        certResolver: letsencrypt
    main-domain:
      entryPoints: ["websecure"]
      rule: "Host(`first_domain`)"
      service: main-service
      middlewares:
        - security-headers
        - check-host-main
      tls:
        certResolver: letsencrypt

    second-domain:
      entryPoints: ["websecure"]
      rule: "Host(`second_domain`)"
      service: second-service
      middlewares:
        - security-headers
        - check-host-second
      tls:
        certResolver: letsencrypt

    panel-route:
      rule: "Host(`first_domain`) || Host(`second_domain`) && (PathPrefix(`/GX5m9Y`) || PathPrefix(`/GX5m9Y/`))"
      service: panel-service
      entryPoints: ["websecure"]
      tls: {}

    sub-route:
      rule: "Host(`first_domain`) || Host(`second_domain`) && (PathPrefix(`/Qz8Q96ZmPW`) || PathPrefix(`/Qz8Q96ZmPW/`) || PathPrefix(`/ZXzTRjH`) || PathPrefix(`/ZXzTRjH/`))"
      service: sub-service
      entryPoints: ["websecure"]
      tls: {}

  services:
    main-service:
      loadBalancer:
        servers:
          - url: https://127.0.0.1:7443
    second-service:
      loadBalancer:
        servers:
          - url: https://127.0.0.1:9443
    panel-service:
      loadBalancer:
        servers:
          - url: http://127.0.0.1:10894
    sub-service:
      loadBalancer:
        servers:
          - url: http://127.0.0.1:18005

  middlewares:
    security-headers:
      headers:
        sslRedirect: true
        stsIncludeSubdomains: true
        stsPreload: true
        stsSeconds: 31536000
        customFrameOptionsValue: "SAMEORIGIN"
        contentTypeNosniff: true
        browserXssFilter: true
        forceSTSHeader: true
        sslForceHost: true

    check-host-main:
      IPAllowList:
        sourceRange:
          - 127.0.0.1/32

    check-host-second:
      IPAllowList:
        sourceRange:
          - 127.0.0.1/32

tcp:
  routers:
    sni-router:
      rule: "HostSNI(`first_domain`) || HostSNI(`second_domain`)"
      service: second-service
      tls:
        passthrough: true

  services:
    second-service:
      loadBalancer:
        servers:
          - address: 127.0.0.1:8443

However, I haven’t been able to get it working. I’m getting "Bad Gateway" errors like this:

{"ClientAddr":"*******","ClientHost":"*********","ClientPort":"******","ClientUsername":"-","DownstreamContentSize":11,"DownstreamStatus":502,"Duration":671292,"OriginContentSize":11,"OriginDuration":405637,"OriginStatus":502,"Overhead":265655,"RequestAddr":"first_domain","RequestContentSize":0,"RequestCount":15,"RequestHost":"first_domain","RequestMethod":"GET","RequestPath":"/","RequestPort":"-","RequestProtocol":"HTTP/2.0","RequestScheme":"https","RetryAttempts":0,"RouterName":"sub-route@file","ServiceAddr":"127.0.0.1:18005","ServiceName":"sub-service@file","ServiceURL":"http://127.0.0.1:18005","SpanId":"0000000000000000","StartLocal":"2025-02-17T14:11:30.923912119Z","StartUTC":"2025-02-17T14:11:30.923912119Z","TLSCipher":"TLS_AES_128_GCM_SHA256","TLSVersion":"1.3","TraceId":"00000000000000000000000000000000","entryPointName":"websecure","level":"info","msg":"","time":"2025-02-17T14:11:30Z"}
ss -tuln | grep 18005
tcp   LISTEN 0      4096               *:18005            *:* 

Can anyone help me figure out what’s going wrong? Any help would be greatly appreciated!

Thanks in advance!

Try setting log: level: DEBUG.

Traefik and all your services are running on the main machine? Without containers?

Traefik is running on the main machine in the docker container, but these two services have been deployed on the host.

{"time":"2025-02-17T17:05:12Z","caller":"log/log.go:245","level":"debug","message":"http: TLS handshake error from 31.178.231.92:62512: EOF"}
{"level":"debug","time":"2025-02-17T17:05:12Z","caller":"github.com/traefik/traefik/v3/pkg/server/service/loadbalancer/wrr/wrr.go:207","message":"Service selected by WRR: c06dc782503af4e3"}
{"level":"debug","time":"2025-02-17T17:05:12Z","caller":"github.com/traefik/traefik/v3/pkg/server/service/loadbalancer/wrr/wrr.go:207","message":"Service selected by WRR: c06dc782503af4e3"}
{"level":"debug","error":"dial tcp 127.0.0.1:41912: connect: connection refused","time":"2025-02-17T17:05:12Z","caller":"github.com/traefik/traefik/v3/pkg/proxy/httputil/proxy.go:117","message":"502 Bad Gateway"}

When you run Traefik in a container, you can not use 127.0.0.1 for target services, as that is localhost only within the container, not of the host/node.

Got it, thanks. I'm just wondering will it worth to deprecate nginx in that case?

When using Docker, check nginx-proxy with companion (Github) or Traefik (example). Both enable reverse proxy configuration via simple env vars or labels on Docker service/container and both handle LetsEncrypt certificates and TLS termination.