Production URLs using localhost

Describe the problem/error/question

I’m having an issue with the resumeURL resolving as localhost instead of my actual domain, but only for production runs. Most instances of this issue I’ve found point to not having WEBHOOK_URL set, but it is here.

-I do not see localhost in the UI anywhere.
-Triggering anything from within the browser / manually works 100%.
-Triggering webhook with test URL and then sending {{ $execution.resumeUrl }} via email = correct domain
-Triggering webhook with prod URL and then sending {{ $execution.resumeUrl }} via email = localhost
.
Not sure what’s missing here.

image

Full compose

version: "3.8"

services:
  n8n:
    image: docker.n8n.io/n8nio/n8n:latest
    restart: unless-stopped
    environment:
      # Public URLs (important behind proxy/tunnel)
      N8N_HOST: ${SUBDOMAIN}.${DOMAIN}
      N8N_PROTOCOL: https
      N8N_PORT: 5678
      WEBHOOK_URL: https://${WHSUBDOMAIN}.${DOMAIN}/
      #N8N_EDITOR_BASE_URL: https://${WHSUBDOMAIN}.${DOMAIN}/
      #N8N_DISABLE_PRODUCTION_MAIN_PROCESS: true

      # Required shared secret
      N8N_ENCRYPTION_KEY: ${N8N_ENCRYPTION_KEY}

      # Timezone (use IANA TZ format)
      GENERIC_TIMEZONE: America/New_York
      TZ: America/New_York

      # Queue mode
      EXECUTIONS_MODE: queue
      QUEUE_BULL_REDIS_HOST: redis
      QUEUE_BULL_REDIS_PORT: 6379

      # Postgres (doc-backed variables)
      DB_TYPE: postgresdb
      DB_POSTGRESDB_HOST: postgres
      DB_POSTGRESDB_PORT: 5432
      DB_POSTGRESDB_DATABASE: ${DB_NAME}
      DB_POSTGRESDB_USER: ${DB_USER}
      DB_POSTGRESDB_PASSWORD: ${DB_PASSWORD}

    ports:
      - "5678:5678"
    volumes:
      - n8n_data:/home/node/.n8n
    depends_on:
      - postgres
      - redis
    networks:
      - n8n_net

  n8n-worker:
    image: docker.n8n.io/n8nio/n8n:latest
    restart: unless-stopped
    command: worker
    environment:
      # Must match main for credentials decryption
      N8N_ENCRYPTION_KEY: ${N8N_ENCRYPTION_KEY}
      GENERIC_TIMEZONE: America/New_York
      TZ: America/New_York

      #values for worker for reference if needed
      #WEBHOOK_URL: https://${WHSUBDOMAIN}.${DOMAIN}/
      #N8N_HOST: ${SUBDOMAIN}.${DOMAIN}
      #N8N_PROTOCOL: https

      # Queue mode (workers connect to same Redis)
      EXECUTIONS_MODE: queue
      QUEUE_BULL_REDIS_HOST: redis
      QUEUE_BULL_REDIS_PORT: 6379

      # Postgres
      DB_TYPE: postgresdb
      DB_POSTGRESDB_HOST: postgres
      DB_POSTGRESDB_PORT: 5432
      DB_POSTGRESDB_DATABASE: ${DB_NAME}
      DB_POSTGRESDB_USER: ${DB_USER}
      DB_POSTGRESDB_PASSWORD: ${DB_PASSWORD}

    depends_on:
      - postgres
      - redis
    volumes:
      - n8n_data:/home/node/.n8n
    networks:
      - n8n_net

  postgres:
    image: postgres:15
    restart: unless-stopped
    environment:
      POSTGRES_USER: ${DB_USER}
      POSTGRES_PASSWORD: ${DB_PASSWORD}
      POSTGRES_DB: ${DB_NAME}
    volumes:
      - postgres_data:/var/lib/postgresql/data
    networks:
      - n8n_net

  redis:
    image: redis:7-alpine
    restart: unless-stopped
    command: ["redis-server", "--appendonly", "yes"]
    volumes:
      - redis_data:/data
    networks:
      - n8n_net

  cloudflared:
    image: cloudflare/cloudflared:latest
    restart: unless-stopped
    command: tunnel --no-autoupdate run --token ${CF_TUNNEL_TOKEN}
    depends_on:
      - n8n
    networks:
      - n8n_net

networks:
  n8n_net:

volumes:
  n8n_data:
  postgres_data:
  redis_data:

Information on your n8n setup

  • n8n version: 2.22.5
  • Database (default: SQLite): postgresdb
  • n8n EXECUTIONS_PROCESS setting (default: own, main):
  • Running n8n via (Docker, npm, n8n cloud, desktop app): docker
  • Operating system: ubuntu

Hi @flowdan

The problem is that your n8n setup is split into two parts: a main controller and a “worker” that handles the actual work for production runs. While you told the main controller what your website address was, the worker was left in the dark because those settings were commented out in your configuration. When the worker tried to create the resume link, it didn’t know your domain name, so it defaulted to “localhost.”

To fix this, you simply need to give the worker the same address information as the main controller. By uncommenting the WEBHOOK_URL, N8N_HOST, and N8N_PROTOCOL lines in the worker section of your Docker file and restarting the service, the worker will now know exactly which domain to use when generating those links.

Here is your corrected compose file

version: "3.8"

services:
  n8n:
    image: docker.n8n.io/n8nio/n8n:latest
    restart: unless-stopped
    environment:
      # Public URLs (important behind proxy/tunnel)
      N8N_HOST: ${SUBDOMAIN}.${DOMAIN}
      N8N_PROTOCOL: https
      N8N_PORT: 5678
      WEBHOOK_URL: https://${WHSUBDOMAIN}.${DOMAIN}/
      #N8N_EDITOR_BASE_URL: https://${WHSUBDOMAIN}.${DOMAIN}/
      #N8N_DISABLE_PRODUCTION_MAIN_PROCESS: true

      # Required shared secret
      N8N_ENCRYPTION_KEY: ${N8N_ENCRYPTION_KEY}

      # Timezone (use IANA TZ format)
      GENERIC_TIMEZONE: America/New_York
      TZ: America/New_York

      # Queue mode
      EXECUTIONS_MODE: queue
      QUEUE_BULL_REDIS_HOST: redis
      QUEUE_BULL_REDIS_PORT: 6379

      # Postgres (doc-backed variables)
      DB_TYPE: postgresdb
      DB_POSTGRESDB_HOST: postgres
      DB_POSTGRESDB_PORT: 5432
      DB_POSTGRESDB_DATABASE: ${DB_NAME}
      DB_POSTGRESDB_USER: ${DB_USER}
      DB_POSTGRESDB_PASSWORD: ${DB_PASSWORD}

    ports:
      - "5678:5678"
    volumes:
      - n8n_data:/home/node/.n8n
    depends_on:
      - postgres
      - redis
    networks:
      - n8n_net

  n8n-worker:
    image: docker.n8n.io/n8nio/n8n:latest
    restart: unless-stopped
    command: worker
    environment:
      # Must match main for credentials decryption
      N8N_ENCRYPTION_KEY: ${N8N_ENCRYPTION_KEY}
      GENERIC_TIMEZONE: America/New_York
      TZ: America/New_York

      # FIXED: These must be active for the worker to generate correct resumeUrls
      WEBHOOK_URL: https://${WHSUBDOMAIN}.${DOMAIN}/
      N8N_HOST: ${SUBDOMAIN}.${DOMAIN}
      N8N_PROTOCOL: https

      # Queue mode (workers connect to same Redis)
      EXECUTIONS_MODE: queue
      QUEUE_BULL_REDIS_HOST: redis
      QUEUE_BULL_REDIS_PORT: 6379

      # Postgres
      DB_TYPE: postgresdb
      DB_POSTGRESDB_HOST: postgres
      DB_POSTGRESDB_PORT: 5432
      DB_POSTGRESDB_DATABASE: ${DB_NAME}
      DB_POSTGRESDB_USER: ${DB_USER}
      DB_POSTGRESDB_PASSWORD: ${DB_PASSWORD}

    depends_on:
      - postgres
      - redis
    volumes:
      - n8n_data:/home/node/.n8n
    networks:
      - n8n_net

  postgres:
    image: postgres:15
    restart: unless-stopped
    environment:
      POSTGRES_USER: ${DB_USER}
      POSTGRES_PASSWORD: ${DB_PASSWORD}
      POSTGRES_DB: ${DB_NAME}
    volumes:
      - postgres_data:/var/lib/postgresql/data
    networks:
      - n8n_net

  redis:
    image: redis:7-alpine
    restart: unless-stopped
    command: ["redis-server", "--appendonly", "yes"]
    volumes:
      - redis_data:/data
    networks:
      - n8n_net

  cloudflared:
    image: cloudflare/cloudflared:latest
    restart: unless-stopped
    command: tunnel --no-autoupdate run --token ${CF_TUNNEL_TOKEN}
    depends_on:
      - n8n
    networks:
      - n8n_net

networks:
  n8n_net:

volumes:
  n8n_data:
  postgres_data:
  redis_data:

Thanks. Initial test appears to have worked, so I think that did the trick.