vps - How to configure NGINX for React frontend and .NET backend using Docker? - Stack Overflow

admin2025-04-29  0

I'm hosting a project on a VPS using Docker. The project consists of:

  • A React frontend (Vite),

  • A .NET backend,

  • NGINX as a reverse proxy.

with a file structure:

project/
├── docker-compose.yml
├── example.client/
│   ├── dockerfile
│   └── build/   
├── example.server/
│   ├── dockerfile
│   └── app/     
└── nginx/
    ├── default.conf
    ├── nginx.conf
    └── certs
        ├── api.example
        │   ├── privkey.pem
        │   └── fullchain.pem 
        └── example
            ├── privkey.pem
            └── fullchain.pem

I want the frontend to work on and the backend on . Both should be served over HTTPS using certificates from Let's Encrypt.

I've managed to:

  • Serve the frontend on .

What I've succesfully achived is frontend to work when going to example and to present the contents of frontend, but then when a request is made, I see in a header address instead of so the outcome is 504 after a timeout and it's driving me crazy. I believe there is something wrong with my nginx config, so here are all the configurations I've been using:

NGINX configuration:

server {
    listen 80;
    server_name example www.example;
    
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    server_name example www.example;
    
    ssl_certificate /etc/nginx/certs/example/fullchain.pem;
    ssl_certificate_key /etc/nginx/certs/example/privkey.pem;
    
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 10m;

    location / {
        proxy_pass http://frontend:80;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection keep-alive;
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
    }

    location /api/ {
        proxy_pass /; 
        proxy_set_header Host api.example;  
        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_read_timeout 90;
        proxy_connect_timeout 90;
        proxy_send_timeout 90;
    }
    
    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log;
}

server {
    listen 80;
    server_name api.example www.api.example;
    return 301 https://$host$request_uri; 
}

server {
    listen 443;
    server_name api.example;

    ssl_certificate /etc/nginx/certs/api.example/fullchain.pem;
    ssl_certificate_key /etc/nginx/certs/api.example/privkey.pem;

    location / {
        proxy_pass :5001;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection keep-alive;
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
    }

    error_log /var/log/nginx/api_error.log;
    access_log /var/log/nginx/api_access.log;
}

docker-compose.yml:

version: '3.9'

services:
  backend:
    image: example.server
    container_name: example.server
    build:
      context: ./example.server
      dockerfile: dockerfile
    ports:
      - "5000:5000"
      - "5001:5001"
    environment:
      - ASPNETCORE_URLS=https://+:5001
      - ASPNETCORE_Kestrel__Certificates__Default__Path=/certs/fullchain.pem
      - ASPNETCORE_Kestrel__Certificates__Default__KeyPath=/certs/privkey.pem
    volumes:
    - /etc/letsencrypt/archive/api.example:/certs:ro
    networks:
      - app-network

  frontend:
    image: example.client
    container_name: example.client
    build:
      context: ./example.client
      dockerfile: dockerfile
    ports:
      - "3000:80"
    depends_on:
      - backend
    networks:
      - app-network
    volumes:
      - /etc/letsencrypt/archive/api.example:/etc/ssl/certs/api.example:ro
      - /etc/letsencrypt/archive/example:/etc/ssl/certs/example:ro

  nginx:
    image: nginx:latest
    container_name: nginx
    restart: always
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
      - ./nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
      - ./nginx/certs/:/etc/nginx/certs:ro
    depends_on:
      - frontend
      - backend
    networks:
      - app-network


networks:
  app-network:
    driver: bridge

What I’ve tried:

  1. Verified the backend works locally on https://localhost:5001.

  2. Confirmed certificates are mounted properly in Docker.

What could be the issue? Is my NGINX configuration wrong, or do I need to adjust Docker networking? How can I fix the 504 Gateway Timeout?

I'm hosting a project on a VPS using Docker. The project consists of:

  • A React frontend (Vite),

  • A .NET backend,

  • NGINX as a reverse proxy.

with a file structure:

project/
├── docker-compose.yml
├── example.client/
│   ├── dockerfile
│   └── build/   
├── example.server/
│   ├── dockerfile
│   └── app/     
└── nginx/
    ├── default.conf
    ├── nginx.conf
    └── certs
        ├── api.example.com
        │   ├── privkey.pem
        │   └── fullchain.pem 
        └── example.com
            ├── privkey.pem
            └── fullchain.pem

I want the frontend to work on https://example.com and the backend on https://api.example.com. Both should be served over HTTPS using certificates from Let's Encrypt.

I've managed to:

  • Serve the frontend on https://example.com.

What I've succesfully achived is frontend to work when going to example.com and to present the contents of frontend, but then when a request is made, I see in a header address https://example.com/api instead of https://api.example.com/api so the outcome is 504 after a timeout and it's driving me crazy. I believe there is something wrong with my nginx config, so here are all the configurations I've been using:

NGINX configuration:

server {
    listen 80;
    server_name example.com www.example.com;
    
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    server_name example.com www.example.com;
    
    ssl_certificate /etc/nginx/certs/example.com/fullchain.pem;
    ssl_certificate_key /etc/nginx/certs/example.com/privkey.pem;
    
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 10m;

    location / {
        proxy_pass http://frontend:80;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection keep-alive;
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
    }

    location /api/ {
        proxy_pass https://api.example.com/api/; 
        proxy_set_header Host api.example.com;  
        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_read_timeout 90;
        proxy_connect_timeout 90;
        proxy_send_timeout 90;
    }
    
    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log;
}

server {
    listen 80;
    server_name api.example.com www.api.example.com;
    return 301 https://$host$request_uri; 
}

server {
    listen 443;
    server_name api.example.com;

    ssl_certificate /etc/nginx/certs/api.example.com/fullchain.pem;
    ssl_certificate_key /etc/nginx/certs/api.example.com/privkey.pem;

    location / {
        proxy_pass https://api.example.com:5001;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection keep-alive;
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
    }

    error_log /var/log/nginx/api_error.log;
    access_log /var/log/nginx/api_access.log;
}

docker-compose.yml:

version: '3.9'

services:
  backend:
    image: example.server
    container_name: example.server
    build:
      context: ./example.server
      dockerfile: dockerfile
    ports:
      - "5000:5000"
      - "5001:5001"
    environment:
      - ASPNETCORE_URLS=https://+:5001
      - ASPNETCORE_Kestrel__Certificates__Default__Path=/certs/fullchain.pem
      - ASPNETCORE_Kestrel__Certificates__Default__KeyPath=/certs/privkey.pem
    volumes:
    - /etc/letsencrypt/archive/api.example.com:/certs:ro
    networks:
      - app-network

  frontend:
    image: example.client
    container_name: example.client
    build:
      context: ./example.client
      dockerfile: dockerfile
    ports:
      - "3000:80"
    depends_on:
      - backend
    networks:
      - app-network
    volumes:
      - /etc/letsencrypt/archive/api.example.com:/etc/ssl/certs/api.example.com:ro
      - /etc/letsencrypt/archive/example.com:/etc/ssl/certs/example.com:ro

  nginx:
    image: nginx:latest
    container_name: nginx
    restart: always
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
      - ./nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
      - ./nginx/certs/:/etc/nginx/certs:ro
    depends_on:
      - frontend
      - backend
    networks:
      - app-network


networks:
  app-network:
    driver: bridge

What I’ve tried:

  1. Verified the backend works locally on https://localhost:5001.

  2. Confirmed certificates are mounted properly in Docker.

What could be the issue? Is my NGINX configuration wrong, or do I need to adjust Docker networking? How can I fix the 504 Gateway Timeout?

Share Improve this question asked Jan 7 at 1:03 mixer519mixer519 111 silver badge1 bronze badge 1
  • You want to answer API requests both at example.com/api and api.example.com/api? Using double SSL encryption (client to api.example.com and then api.example.com to backend:5001 makes a little sense (just a waste of resources). I'm not sure, but if it is a Docker networking issue, you can try to replace both proxy_pass https://api.example.com/api/; and proxy_pass https://api.example.com:5001; lines with proxy_pass https://backend:5001; – Ivan Shatsky Commented Jan 7 at 3:01
Add a comment  | 

1 Answer 1

Reset to default 0

Normally, the SSL-termination is handled by your nginx-reverse-proxy. So the backend shouldn't know the certificates.

Keep that in mind and your nginx-config should be look like this:

server {
    listen 80;
    server_name example.com www.example.com;
    
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    server_name example.com www.example.com;
    
    ssl_certificate /etc/nginx/certs/example.com/fullchain.pem;
    ssl_certificate_key /etc/nginx/certs/example.com/privkey.pem;
    
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 10m;

    location / {
        proxy_pass http://frontend:80;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection keep-alive;
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
    }

    location /api/ {
        proxy_pass http://backend:5000/api/;
        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_read_timeout 90;
        proxy_connect_timeout 90;
        proxy_send_timeout 90;
    }
    
    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log;
}

In your fronted you can now easily access the backend via /api/.

转载请注明原文地址:http://anycun.com/QandA/1745935877a91348.html