Connection refused while connecting to upstream. Phoenix server behind Nginx on a single VPS

I’m trying to deploy a Phoenix liveview app behind Ngnix on a single VPS. I tried to get Phoenix to serve HTTP, while Ngnix handles HTTPS/SSL. However, I’m getting 502 bad gateway with a “Connection refused while connecting to upstream” error. Both the Phoenix server and Ngnix are running in docker containers. Does anybody know what could be going wrong?

Elixir: 1.13.4-otp-25
Phoenix: 1.6.11
Phoenix_live_view: 0.17.11

Logs from sudo docker logs --tail 50 --follow --timestamps <nginx-container-hash> when I go to my.site.com:

2025-05-11T08:51:05.331109428Z 2025/05/11 08:51:05 [error] 21#21: *52 connect() failed (111: Connection refused) while connecting to upstream, client: 208.123.61.54, server: my.site.com, request: "GET / HTTP/1.1", upstream: "http://127.0.0.1:4001/", host: "my.site.com"
2025-05-11T08:51:05.331152962Z 2025/05/11 08:51:05 [warn] 21#21: *52 upstream server temporarily disabled while connecting to upstream, client: 208.123.61.54, server: my.site.com, request: "GET / HTTP/1.1", upstream: "http://127.0.0.1:4001/", host: "my.site.com"
2025-05-11T08:51:05.331160447Z 2025/05/11 08:51:05 [error] 21#21: *52 connect() failed (111: Connection refused) while connecting to upstream, client: 208.123.61.54, server: my.site.com, request: "GET / HTTP/1.1", upstream: "http://127.0.0.1:4001/", host: "my.site.com"
2025-05-11T08:51:05.331247276Z 2025/05/11 08:51:05 [warn] 21#21: *52 upstream server temporarily disabled while connecting to upstream, client: 208.123.61.54, server: my.site.com, request: "GET / HTTP/1.1", upstream: "http://127.0.0.1:4001/", host: "my.site.com"
2025-05-11T08:51:05.331262155Z 208.123.61.54 - - [11/May/2025:08:51:05 +0000] "GET / HTTP/1.1" 502 157 "-" "Mozilla/5.0 (iPhone; CPU iPhone OS 17_6_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) FxiOS/138.0  Mobile/15E148 Safari/605.1.15" "-"


prod.exs:

config :mysite, MysiteWeb.Endpoint,
  cache_static_manifest: "priv/static/cache_manifest.json",
  url: [host: "my.site.com", port: 4000]
  http: [ip: {0, 0, 0, 0}, port: 4000],
  check_origin: false,
  server: true,
  check_origin: ["https://my.site.com"],
  secret_key_base: Map.fetch!(System.get_env(), "SECRET_KEY_BASE")

config :mysite, MySite.Repo,
  start_apps_before_migration: [:ssl],
  adapter: Ecto.Adapters.Postgres,
  url: System.get_env("DATABASE_URL"),
  ssl: true,
  pool_size: 10


Nginx:

server {
    listen 443 default_server ssl;
    listen [::]:443 ssl;

    server_name my.site.com;

    ssl_certificate /etc/nginx/ssl/live/my.site.com/fullchain.pem;
    ssl_certificate_key /etc/nginx/ssl/live/my.site.com/privkey.pem;

    location / {
             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_set_header X-Forwarded-Proto  $scheme;
             proxy_set_header X-Forwarded-Host  $host;
             proxy_set_header X-Forwarded-Port  $server_port;
             proxy_pass http://localhost:4001/;
             proxy_redirect off;
    }
    # required for websocket
    location /live {
             proxy_http_version 1.1;
             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;
             proxy_set_header X-Forwarded-Proto  $scheme;
             proxy_set_header X-Forwarded-Host  $host;
             proxy_set_header X-Forwarded-Port  $server_port;
             proxy_pass http://localhost:4001/live;
             # raise the proxy timeout for the websocket
             proxy_read_timeout 6000s;
    }
}


Starting Phoenix server:

$ sudo docker run -p 4001:4000  --env-file .env collaborlist:v1
09:25:15.666 [info] Running CollaborlistWeb.Endpoint with cowboy 2.9.0 at :::4000 (http)
09:25:15.667 [info] Access CollaborlistWeb.Endpoint at http://0.0.0.0:4000
09:25:15.797 [warning] Description: 'Server authenticity is not verified since certificate path validation is not enabled'
     Reason: 'The option {verify, verify_peer} and one of the options \'cacertfile\' or \'cacerts\' are required to enable this.'

I am not a docker expert (far from it), but at first glance it looks like you’re trying to connect to localhost (from nginx), but since nginx is running inside it’s own container - localhost is not the same as phoenix uses (since phoenix runs inside other container).

Maybe some “reverse port forwarding” (or however it’s called in docker-land) between those two containers?

I’m sorry if my bad english and lack of proper docker knowledge confused you even more :sweat_smile:

2 Likes

If you run them with compose then you can refer to their names. So you nginx config is http://my-elixir-service-name.

Sorry on phone right now

1 Like

The solution required 2 steps (a combination of Egis and FlyingNoodle’s comments).

  1. Adding the Phoenix application as another service to the docker-compose.yml
  2. Creating an upstream block for the Phoenix sever in Nginx conf file.

These are the working docker-compose.yml and nginx.conf files:

nginx.conf

upstream docker-web {
    server web:4000;
      }

server {
    listen 443 default_server ssl;
    listen [::]:443 ssl;

    server_name my.site.com;

    ssl_certificate /etc/nginx/ssl/live/my.site.com/fullchain.pem;
    ssl_certificate_key /etc/nginx/ssl/live/my.site.com/privkey.pem;

    location / {
             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_set_header X-Forwarded-Proto  $scheme;
             proxy_set_header X-Forwarded-Host  $host;
             proxy_set_header X-Forwarded-Port  $server_port;
             proxy_pass http://docker-web;
             proxy_redirect off;
    }
    # required for websocket
    location /live {
             proxy_http_version 1.1;
             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;
             proxy_set_header X-Forwarded-Proto  $scheme;
             proxy_set_header X-Forwarded-Host  $host;
             proxy_set_header X-Forwarded-Port  $server_port;
             proxy_pass http://docker-web/live;
             # raise the proxy timeout for the websocket
             proxy_read_timeout 6000s;
    }
}


docker-compose.yml

services:
  web:
    image: mysite:v1
    ports:
      - 4000:4000
    env_file: "webapp.env"
    networks:
        main:
            aliases:
                - web
  proxy:
    image: nginx:latest
    ports:
      - 80:80
      - 443:443
    restart: always
    links:
      - web:web
    depends_on:
      - web
    volumes:
      - ./nginx/conf/:/etc/nginx/conf.d/:ro
      - ./certbot/conf/live:/etc/nginx/ssl/live/:ro
      - ./certbot/conf/archive:/etc/nginx/ssl/archive/:ro
      - ./certbot/www/:/var/www/certbot/:ro
    networks:
        main:
            aliases:
                - proxy
  certbot:
    image: certbot/certbot:latest
    volumes:
      - ./certbot/www/:/var/www/certbot/:rw
      - ./certbot/conf/:/etc/letsencrypt/:rw

networks:
  main:

2 Likes

No worries! You were correct about what the issue was. Your reply was able to lead me down the correct path to solve the problem :blush:

1 Like