skip to Main Content

I have a fairly standard ReactJS frontend (using port 3000) app which is served by a NodeJS backend server (using port 5000). Both apps are Dockerized and I have configured NGINX in order to proxy requests from the frontend to and from the server.

Dockerfile for front end (with NGINX "baked in"):

FROM node:lts-alpine as build

WORKDIR /app

COPY ./package.json ./
COPY ./package-lock.json ./

RUN npm install
COPY . .
RUN npm run build

FROM nginx

EXPOSE 3000
EXPOSE 443
EXPOSE 80

COPY ./cert/app.crt /etc/nginx/
COPY ./cert/app.key /etc/nginx/

ENV HTTPS=true
ENV SSL_CRT_FILE=/etc/nginx/app.crt
ENV SSL_KEY_FILE=/etc/nginx/app.key

RUN rm /etc/nginx/conf.d/default.conf

COPY ./default.conf /etc/nginx/nginx.conf
COPY --from=build /app/build/ /usr/share/nginx/html

CMD ["nginx", "-g", "daemon off;"]

Dockerfile for server:

FROM node:lts-alpine as build

WORKDIR /app
EXPOSE 5000

ENV NODE_TLS_REJECT_UNAUTHORIZED=0
ENV DANGEROUSLY_DISABLE_HOST_CHECK=true
ENV NODE_CONFIG_DIR=./config/

COPY ./package.json ./
COPY ./package-lock.json ./

RUN npm install

COPY . .
CMD [ "npm", "start" ]

The docker-compose.yml for this setup is

version: '3.8'
services:
  client:
    container_name: client
    depends_on:
      - server
    stdin_open: true
    environment:
      - CHOKIDAR_USEPOLLING=true
      - HTTPS=true
      - SSL_CRT_FILE=/etc/nginx/app.crt
      - SSL_KEY_FILE=/etc/nginx/app.key
    build:
      dockerfile: Dockerfile
      context: ./client
    expose:
      - "8000"
      - "3000"
    ports:
      - "3000:443"
      - "8000:80"
    volumes:
      - ./client:/app
      - /app/node_modules
      - /etc/nginx
    networks:
      - internal-network

  server:
    container_name: server
    build:
      dockerfile: Dockerfile
      context: "./server"
    expose:
      - "5000"
    ports:
      - "5000:5000"
    volumes:
      - /app/node_modules
      - ./server:/app
    networks:
      - internal-network

networks:
  internal-network:
    driver: bridge
    

And crucially, the NGINX default.conf is

worker_processes auto;

events {
  worker_connections 1024;
}

pid /var/run/nginx.pid;

http {

    include mime.types;

    upstream loadbalancer {
        server server:5000 weight=3;
    }

    server {
        listen 80 default_server;
        listen [::]:80 default_server;

        server_name _;

        port_in_redirect off;
        absolute_redirect off;

        return 301 https://$host$request_uri;
    }

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

        server_name example.app* example.co* example.uksouth.azurecontainer.io* localhost*;
        error_page 497 https://$host:$server_port$request_uri;

        error_log /var/log/nginx/client-proxy-error.log;
        access_log /var/log/nginx/client-proxy-access.log;

        ssl_protocols              TLSv1 TLSv1.1 TLSv1.2;
        ssl_ciphers                ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:ECDHE-RSA-RC4-SHA:ECDHE-ECDSA-RC4-SHA:AES128:AES256:RC4-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!3DES:!MD5:!PSK;
        ssl_prefer_server_ciphers  on;
        ssl_session_cache          shared:SSL:10m;
        ssl_session_timeout        24h;

        keepalive_timeout 300;
        add_header Strict-Transport-Security 'max-age=31536000; includeSubDomains';

        ssl_certificate     /etc/nginx/app.crt;
        ssl_certificate_key /etc/nginx/app.key;

        root /usr/share/nginx/html;
        index index.html index.htm index.nginx-debian.html;

         location / {
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection 'upgrade';
            proxy_set_header Host $host;
            proxy_cache_bypass $http_upgrade;
            try_files $uri $uri/ /index.html;
        }
        
        location /tours {
            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_pass http://loadbalancer;
        }
    }
}

with this configuration I have two problems:

  1. By running docker-compose up -d, this setup builds and deploys two Docker containers locally. When I use https://localhost:3000/id this works and the data is retrieved and shown in browser correctly – when I type http://localhost:3000/id this gets redirected to http://localhost:443/id and this does not work. I have attempted to use NGINX commands port_in_redirect off; absolute_redirect off; but this has not helped. How can I make sure that the redirect does not edit the port number? (this is likely not going to be an issue in production where the port numbers are not used).

  2. The bigger problem: the deployment to Azure is done using a docker context and running docker-compose -f ./docker-compose-azure.yml up. This runs and creates two Docker containers and a side-car process. The docker-compose-azure.yml file is

    version: ‘3.8’
    services:

      client:
        image: dev.azurecr.io/example-client
        depends_on:
          - server
        stdin_open: true
        environment:
          - CHOKIDAR_USEPOLLING=true
          - HTTPS=true
          - SSL_CRT_FILE=/etc/nginx/app.crt
          - SSL_KEY_FILE=/etc/nginx/app.key
        restart: unless-stopped
        domainname: "example-dev"
        expose:
          - "3000"
        ports:
          - target: 3000
            #published: 3000
            protocol: tcp 
            mode: host
        networks:
          - internal-network
    
      server:
        image: dev.azurecr.io/example-server
        restart: unless-stopped
        ports:
          - "5000:5000"
        networks:
          - internal-network
    
    networks:
      internal-network:
        driver: bridge
    

If I don’t use HTTPS and a simple reverse proxy – the two issues outline above go away. But with the configuration above, calls to the Azure FQDN/URL fail; HTTPS requests timing out "ERR_CONNECTION_TIMED_OUT", and for HTTP, the site could not be found. What am I doing wrong here?

Thanks for your time.

3

Answers


  1. I think you need to check/update Nginx configuration file properly and also make sure SSL certificate files are available

    # http block would be
    server {
            listen 80 default_server;
            return 301 https://$server_name$request_uri;
    }
    

    and in https server block, you need to update location block

    location /tours {
            proxy_pass http://server:5000;
            proxy_set_header Connection "";
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $remote_addr;
    }
            
    location / {
            try_files $uri $uri/ /index.html;
    }
    

    Updated

    Your Nginx config file would be

    worker_processes auto;
    
    events {
      worker_connections 1024;
    }
    
    pid /var/run/nginx.pid;
    
    http {
    
        include mime.types;
    
        server {
            listen [::]:443 ssl;
            listen 443 ssl;
    
            server_name my-redirected-domain.com my-azure-domain.io localhost;
    
            access_log /var/log/nginx/client-proxy.log;
    
            ssl_protocols              TLSv1 TLSv1.1 TLSv1.2;
            ssl_ciphers                ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:ECDHE-RSA-RC4-SHA:ECDHE-ECDSA-RC4-SHA:AES128:AES256:RC4-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!3DES:!MD5:!PSK;
            ssl_prefer_server_ciphers  on;
            ssl_session_cache          shared:SSL:10m;
            ssl_session_timeout        24h;
    
            keepalive_timeout 300;
            add_header Strict-Transport-Security 'max-age=31536000; includeSubDomains';
    
            ssl_certificate     /etc/nginx/viewform.app.crt;
            ssl_certificate_key /etc/nginx/viewform.app.key;
    
            root /usr/share/nginx/html;
            index index.html index.htm index.nginx-debian.html;
    
            location / {
                try_files $uri $uri/ /index.html;
            }
            
            location /tours {
                proxy_pass http://server:5000;
                proxy_set_header Connection "";
                proxy_set_header Host $host;
                proxy_set_header X-Real-IP $remote_addr;
                proxy_set_header X-Forwarded-For $remote_addr;
            }
        }
    
        server {
            listen 80 default_server;
            return 301 https://$server_name$request_uri;
        }
    }
    
    Login or Signup to reply.
  2. Use port 443 everywhere to avoid any confusion with port remapping (that can be an advance setup):

    1.) Define client container to be running on port 443:

    version: '3.8'
    services:
      client:
    ...
        ports:
          - port: 443
            protocol: TCP
    

    2.) Define Nginx to be running on the port 443 with proper TLS setup as you have in your updated nginx.conf

    Deploy and open https://<public IP> (you will very likely need to add sec. exception in the browser).

    BTW: Azure has quite good article about Nginx with TLS (but more advance setup is used):
    https://learn.microsoft.com/en-us/azure/container-instances/container-instances-container-group-ssl

    IMHO better redirect from http to https is:

    server {
        listen         [::]:80;
        return 301 https://$host$request_uri;
    }
    
    Login or Signup to reply.
  3. I think Jan Garaj‘s answer has touched upon all the important bits. Here is my take, trying to give a targeted answer.

    HTTP to HTTPS redirect

    Currently the return 301 statement is using the $host variable that only holds the Hostname and not the port information. To capture both, you can use the $http_host variable instead. source

    server {
        listen         [::]:80;
    
        #//307 to preserve POST data
        return 307 https://$http_host$request_uri; 
    }
    

    Problems with the Azure config

    In the Azure config, you have this bit:

        ports:
          - target: 3000
            #published: 3000
            protocol: tcp 
            mode: host
    

    which identifies 3000 as the internal client port which listens to the requests. But you have to remember that you have a NGINX proxy inside that only listens to ports 80 or 443 (the server blocks in Nginx config). So this is the reason you get the ERR_CONNECTION_TIMED_OUT error because the requests are sent to port 3000 where nothing is listening.

    As you want to do a HTTPS deployment, you can set this to 443 and the Nginx will take care of the request.

    enabling HTTP redirect on Azure

    The final bit is to configure the Azure deployment such that when a HTTP request is made to your URL, it should get redirected to the HTTPS counterpart. We already have the NGINX redirect block for port 80.

    BUT, it will not help. As we specify the target to be 443 inside the container, the HTTP request will try to hit 443 and get refused.This article also mentions the same towards the end

    Use your browser to navigate to the public IP address of the container group. The IP address shown in this example is 52.157.22.76, so the URL is https://52.157.22.76. You must use HTTPS to see the running application, because of the Nginx server configuration. Attempts to connect over HTTP fail.

    This could be solved if it were possible to add another port to Azure config, the port 80.

         ports:
          - port: 443
            protocol: TCP
          - port: 80
            protocol: TCP
    

    I am not sure if Azure allows this, but if it does then thats the final solution.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search