Nginx reverse proxy and multiple React apps - reactjs

So I'm trying to use NGINX as a reverse proxy for 2 react apps and 1 node js api. Each in separate docker containers.
So for example,
localhost -> leads to one react app
localhost/admin -> leads to another react app
localhost/api/getProducts -> leads to the /getProducts endpoint of the api
The first example and the second both work as intended. No issues. It's the 2nd example I'm having trouble configuring. It should just lead to a dashboard application built in React, but all I get is a white screen (with the same favicon as the first react app).
Here is my nginx config file
upstream api {
least_conn;
server api:8080 max_fails=3 fail_timeout=30s;
}
upstream app {
least_conn;
server app:3000 max_fails=3 fail_timeout=30s;
}
upstream adminapp {
least_conn;
server adminapp:3001 max_fails=3 fail_timeout=30s;
}
server {
listen 80;
if ($request_method = 'OPTIONS') {
return 200;
}
# To allow POST on static pages
error_page 405 =200 $uri;
location / {
proxy_pass http://app;
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;
expires 30d;
break;
}
location ~ /admin/(?<url>.*) {
proxy_pass http://adminapp;
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;
expires 30d;
break;
}
location ~ /api/(?<url>.*) {
proxy_pass http://api/$url;
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;
}
location /health-check {
return 200;
access_log off;
}
}
}
When I specifically go to localhost:3001, I can reach the admin dashboard so I know it's running perfectly fine.
Here's my docker compose file as well
version: '3.7'
services:
nginx:
container_name: nginx
image: nginx
ports:
- '80:80'
- '443:443'
links:
- api:api
- app:app
- adminapp:adminapp
volumes:
- ./server/config/nginx:/etc/nginx
- ./server/config/certs:/etc/ssl/private
app:
container_name: app
build:
context: ./frontend
dockerfile: Dockerfile
volumes:
- './frontend:/usr/app/frontend/'
- '/usr/app/frontend/node_modules'
ports:
- '3000:3000'
environment:
- NODE_ENV=development
adminapp:
container_name: adminapp
build:
context: ./admin
dockerfile: Dockerfile
volumes:
- './admin:/usr/app/admin/'
- '/usr/app/admin/node_modules'
ports:
- '3001:3001'
environment:
- NODE_ENV=development
- PORT=3001
api:
container_name: api
build:
context: ./backend
dockerfile: Dockerfile
volumes:
- './backend:/usr/app/backend/'
- '/usr/app/backend/node_modules'
ports:
- '8080'
environment:
- NODE_ENV=development

So this isnt an answer really - because im having the same problem - but if you try setting an PUBLIC_URL as an env variable with "./", or "./your-path", you should see some changes. If you inspect the source of the page and click on the URLs of the static resources you should see the JS there. You may have to modify the path by adding /adminapp before it. I'm going to continue working on it so I'll update with anything else I find. Also FYI this may be relevant:
https://github.com/facebook/create-react-app/issues/8222#issuecomment-568308139

Leaving an answer here because I figured it out. Basically you have to add "homepage": "." to your package.json first. Then, make sure you check your dockerfile. This was my mistake. For it to work on a subdomain you have to explicitly build everything in the Dockerfile as it is here:
FROM node:14-alpine as build-deps
WORKDIR /usr/src/app
COPY package.json yarn.lock ./
RUN yarn
COPY . ./
RUN yarn build
FROM nginx:1.13-alpine
#WORKDIR /usr/src/app
COPY --from=build-deps /usr/src/app/build /usr/share/nginx/html
COPY ./nginx.conf /etc/nginx/nginx.conf
EXPOSE 3000
This is how I had it before, and this was incorrect:
# linux distro
FROM node:14-alpine
# workdir inside the container
WORKDIR /usr/src/app
# copy these files to WORKDIR
COPY package.json .
COPY yarn.lock .
# Install all of the npm dependencies on the Docker image.
RUN yarn install
# Copy everything from the folder which this Dockerfile is placed
# and unto the new containers WORKDIR
COPY . .
# Open port 3000
EXPOSE 3000
# Run the script "npm start" defined in package.json
# this will start the dev server and the project will be at the containers ip+ port.
CMD ["yarn", "start"]
If you make sure that you have your Dockerfile correct and you have "homepage", the paths should be correct and your react app should work at the subdomain.
As a side note, if you are having trouble with react routing, check your basepath, that will probably fix it for you. =)

So I realized I never posted how I solved this; and to be honest I forget exactly what I did. I compared the nginx config in my original question to what I have now and realized there were some stark differences. So I'm posting what I have now. I don't think I had to change my docker config or anything. I did switch to the staticfloat/nginx-certbot image for SSL.
What it looks like I did though was set up a separate server block for the subdomain. Not sure if that is the most efficient or whatever, but it ended up working.
server {
listen 80;
server_name domain.com www.domain.com;
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443;
server_name admin.domain.com;
ssl_certificate /etc/letsencrypt/live/admin.domain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/admin.domain.com/privkey.pem;
location / {
proxy_pass http://adminapp;
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;
expires 1d;
break;
}
location ~* \.(eot|otf|ttf|woff|woff2)$ {
expires 365d;
add_header Access-Control-Allow-Origin *;
}
location ~ /api/(?<url>.*) {
proxy_pass http://api/$url;
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;
}
location /health-check {
return 200;
access_log off;
}
}
server {
listen 443 ssl default_server;
# listen 80 default_server;
server_name domain.com;
if ($request_method = 'OPTIONS') {
return 200;
}
ssl_certificate /etc/letsencrypt/live/domain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/domain.com/privkey.pem;
# To allow POST on static pages
error_page 405 =200 $uri;
location / {
proxy_pass http://app;
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;
expires 1d;
break;
}
location ~* \.(eot|otf|ttf|woff|woff2)$ {
expires 365d;
add_header Access-Control-Allow-Origin *;
}
location ~ /api/(?<url>.*) {
proxy_pass http://api/$url;
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;
}
location /health-check {
return 200;
access_log off;
}
}
server {
listen 443 ssl;
# listen 80 default_server;
server_name www.domain.com;
if ($request_method = 'OPTIONS') {
return 200;
}
ssl_certificate /etc/letsencrypt/live/www.domain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/www.domain.com/privkey.pem;
# To allow POST on static pages
error_page 405 =200 $uri;
location / {
proxy_pass http://app;
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;
expires 1d;
break;
}
location ~* \.(eot|otf|ttf|woff|woff2)$ {
expires 365d;
add_header Access-Control-Allow-Origin *;
}
location ~ /api/(?<url>.*) {
proxy_pass http://api/$url;
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;
}
location /health-check {
return 200;
access_log off;
}
}

Related

How to host multiple create-react-app development servers under nginx with working live (hot) reload

I am developing a website with React.js for the frontend and have 2 separate apps for the users and the admins. The users will be under example.com and the admins under example.com/admin.
I am developing both apps behind an nginx server as a reverse proxy. I have had no issue developing a single app behind nginx, but I cannot use hot reload for the 2nd app. The app is served properly, with the only exception that the hot reload does not work.
I have HTTPS=true on both my .env files of the React.js apps. The main app's hot reload works fine, but the /admin app's hot reload fails with the error Firefox can’t establish a connection to the server at wss://192.168.1.2/adminws (developing through local network, so I can test the apps on my phone as well, but the hot reload won't work on the localhost either).
The main app is hosted under port 3000, the admin app is hosted under port 4000.
This is what my main app's .env looks like:
HTTPS=true
WDS_SOCKET_PORT=443
FAST_REFRESH=true
This is what my admin app's .env looks like:
HTTPS=true
WDS_SOCKET_PORT=443
WDS_SOCKET_PATH=/adminws
FAST_REFRESH=true
This is what my nginx configuration file looks like:
server {
# listen 80 default_server;
# listen [::]:80 default_server;
# SSL configuration
#
listen 443 ssl http2 default_server;
listen [::]:443 ssl default_server;
ssl on;
ssl_certificate /etc/nginx/ssl/localhost.crt;
ssl_certificate_key /etc/nginx/ssl/localhost.key;
gzip on;
gzip_types text/plain application/xml application/json;
gzip_proxied any;
gzip_min_length 1000;
gunzip on;
gzip_static on;
root /var/www/html;
# Add index.php to the list if you are using PHP
index index.html index.htm index.nginx-debian.html;
server_name _;
location /ws {
proxy_pass https://127.0.0.1:3000;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
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;
}
location /adminws {
proxy_pass https://127.0.0.1:4000;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
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;
}
location /api {
proxy_pass http://127.0.0.1:3200;
}
location /admin {
proxy_pass https://127.0.0.1:4000;
}
location / {
proxy_pass https://127.0.0.1:3000;
}
}
I should note that the admin app's hot reload works properly when I remove both WDS_SOCKET_PORT and WDS_SOCKET_PATH from the .env file and run it on https://localhost:4000/admin, but this way I would not be able to test it behind nginx.
I removed both WDS_SOCKET_PORT and WDS_SOCKET_PATH from the admin app's .env file and it now seems to be working properly. Everything else seems to be ok.

docker React, django(gunicorn), nginx reverse proxy with https give Bad request 400 on accessing backend APIs

I have containerized application with following containers working together with docker-compose
Nginx - reverse proxy
React - frontend
Django served using gunicorn - backend
When using http all works well with frontend and backend. I used letsencrypt certbot to generate ssl certificates. On switching to https, fronend seems to work fine however none of backend apis are working. Any request to backend such as login generates 'Bad Request 400' response.
I have tried following options but none worked.
a. Switching Django to production and adding ALLOWED_HOSTS=['*'] or specific domain
b. Adding upstream in nginx.conf
c. disableHostCheck: true in webpack devServer.
Following are my docker-compose and nginx.conf respectively
docker-compose.yml
services:
reverse_proxy:
image: nginx:1.17.10
volumes:
- ./reverse_proxy/nginx.conf:/etc/nginx/nginx.conf
- ./reverse_proxy/certbot-etc:/etc/letsencrypt
- ./reverse_proxy/certbot-var:/var/lib/letsencrypt
ports:
- 80:80
- 443:443
depends_on:
- webapp
- frontend
webapp:
build : ./backend/
command: gunicorn --bind 0.0.0.0:8000 ai.wsgi:application
expose:
- 8000
volumes:
- ./backend/:/app
ports:
- 8000:8000
frontend:
build: ./frontend/ai-web/
command: npm run ai
ports:
- 3000:3000
nginx.conf
worker_processes auto;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;
events {
worker_connections 1024;
}
http {
fastcgi_read_timeout 3000;
proxy_read_timeout 3000;
client_max_body_size 1000M;
upstream django {
server webapp:8000;
}
server {
listen 80;
server_name localhost 127.0.0.1;
location / {
proxy_pass http://frontend:3000;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Real-IP $remote_addr;
}
location /api/ {
proxy_pass http://webapp:8000;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Real-IP $remote_addr;
}
}
server {
listen 443 ssl;
server_name my_domain;
ssl_protocols TLSv1.2 TLSv1.3;
location / {
proxy_pass http://frontend:3000;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto "https";
}
location /api/ {
proxy_pass http://webapp:8000;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto "https";
}
ssl_certificate /etc/letsencrypt/live/my_domain/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/my_domain/privkey.pem;
}
}
Am I missing opening container port while using https ?
Please provide any pointers.

How to redirect multiple apps in Nginx reverse proxy on docker?

I am running nginx reverse proxy on docker together with two React.js apps. I want to make it so that both apps are accessible via the same port by just adding /app1 or /app2. When I had only http, it worked but now i added ssl and it does not work.
Here is my default.conf
server {
listen 80;
listen [::]:80;
server_name xxx.xxx.xxx.xx;
if ($scheme = http) {
return 301 https://$server_name$request_uri;
}
}
server {
listen 443 ssl; # 'ssl' parameter tells NGINX to decrypt the traffic
server_name xxx.xxx.xxx.xx;
ssl on;
ssl_certificate xxx.xxx.xxx.xx.pem;
ssl_certificate_key xxx.xxx.xxx.xx-key.pem;
ssi on;
proxy_intercept_errors on;
location /app1 {
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://app1:5000;
# WebSocket support
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
location /app2 {
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://app2:5000;
# WebSocket support
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
Here is my docker-compose.yml
version: "3.1"
services:
app1:
stdin_open: true
build: app1/app1
networks:
- local-network
environment:
- NODE_ENV=production
- PORT=5000
app2:
stdin_open: true
build: app2/app2
networks:
- local-network
environment:
- NODE_ENV=production
- PORT=5000
micro-frontend-nginx:
container_name: micro-frontend-nginx
build: nginx
volumes:
- ./assets:/var/www
ports:
- "8080:80"
- "443:443"
networks:
- local-network
depends_on:
- app1
- app2
networks:
local-network:
Those are the files where I specify any ports. Could you tell me why it fails to redirect the apps properly?
UPDATE:
ERROR LOG:
2020/10/30 12:40:30 [error] 7#7: *1 SSL_do_handshake() failed (SSL:
error:140770FC:SSL routines:SSL23_GET_SERVER_HELLO:unknown protocol)
while SSL handshaking to upstream, client: 192.168.160.1, server:
192.168.178.94, request: "GET /app2 HTTP/1.1", upstream: "https://192.168.160.2:5000/app2", host: "192.168.178.94"

How to deploy NextJS with NGINX?

So I know how to deploy a React app on a server.
npm run build
create a server block and point the root to my react app folder build (root /var/www/xfolder/build;)
systemctl restart nginx
run my node server (nohup node server &&) and its done.
I feel kind of dumb for not understanding this with NextJS. I run npm run build
I'm expecting something like a build folder. I've tried setting the server block root to
/var/www/xfolder/.next but the page still gives 403 forbidden. And do I need to run npm run start? I'm confuse on how to properly deploy the app. I'm using Ubuntu, NginX (1gb droplet) in DigitalOcean.
Check this: https://gist.github.com/iam-hussain/2ecdb934a7362e979e3aa5a92b181153
Reference for HTTP/HTTPS: https://gist.github.com/iam-hussain/2ecdb934a7362e979e3aa5a92b181153
Start PM2 nextJS service on port 8080:
cd PROJECT_DIRECTORY
pm2 start "npm run start -- -p 8080" --name contractverifier
Configure Nginx:
Replace this file with the below code /etc/nginx/sites-available/default
server {
server_name www.DOMAINNAME.com DOMAINNAME.com;
index index.html index.htm;
root /home/ubuntu/PROJECT_FOLDER; #Make sure your using the full path
# Serve any static assets with NGINX
location /_next/static {
alias /home/ubuntu/PROJECT_FOLDER/.next/static;
add_header Cache-Control "public, max-age=3600, immutable";
}
location / {
try_files $uri.html $uri/index.html # only serve html files from this dir
#public
#nextjs;
add_header Cache-Control "public, max-age=3600";
}
location #public {
add_header Cache-Control "public, max-age=3600";
}
location #nextjs {
# reverse proxy for next server
proxy_pass http://localhost:8080; #Don't forget to update your port number
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;
}
listen 80 default_server;
listen [::]:80;
}
I managed to make it work. The problem is on my Nginx server block. I just add this block
location / {
proxy_pass http://localhost:3000;
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;
}
then run
npm start
I prefer to pm2 in order to start nextJs service and Nginx for publishing it
pm2 cmd:
pm2 start yarn --name nextjs --interpreter bash -- start
pm2 show nextjs
You can push that config into /etc/nginx/conf.d/your-file.config
/etc/nginx/nginx.config
server {
listen 80; # you can use 443 and letsencrypt to get SSL for free
server_name dicom-interactive.com; # domain name
access_log /var/log/dicom-interactive/access.log; # mkdir dir first
error_log /var/log/dicom-interactive/error.log error;
# for public asset into _next directory
location _next/ {
alias /srv/udemii-fe/.next/;
expires 30d;
access_log on;
}
location / {
# reverse proxy for next server
proxy_pass http://localhost:8000; # your nextJs service and port
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;
# we need to remove this 404 handling
# because next's _next folder and own handling
# try_files $uri $uri/ =404;
}
}

AWS front-end back-end communication

I have 2 apps running on ASW Symfony on port 8000 (local) and react 3000(local) but accessible through TCP on port 80 redirections was achieved by listening of port 80 within nginx server.
server {
listen 80;
server_name example.info www.example.info;
location / {
proxy_pass http://127.0.0.1:3000;
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;
proxy_redirect off;
}
}
server {
listen 8000;
server_name example.info www.example.info;
location / {
proxy_pass http://127.0.0.1:8000;
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;
proxy_redirect off;
}
}
I have tried to listen and redirect of two ports but without success.
Within the server, Symfony application is accessible with curl http://127.0.0.1:8000
From outside in my react app I am sending api requests to asw.external.ip (123.123.123.123:800) but I get timeout. How could I access my back-end from outside?
AWS ElasticBeanstalk - Configuring the Proxy Server to your back-end
You can use this config file to your Aws Ec2 as well.
/etc/nginx/conf.d/proxy.conf
upstream nodejs {
server 127.0.0.1:5000;
keepalive 256;
}
server {
listen 8080;
access_log /var/log/nginx/access.log main;
location / {
proxy_pass http://nodejs;
proxy_set_header Connection "";
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
gzip on;
gzip_comp_level 4;
gzip_types text/html text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;
## Optional configuration if you want to allow AWS
## to cache your static files
location /static {
alias /var/app/current/static;
}
}
Edit - Configuring Nginx for Symfony
server {
listen 8080;
server_name sf2testproject.dev;
root /home/maurits/public_html/web;
location / {
# try to serve file directly, fallback to rewrite
try_files $uri #rewriteapp;
}
location #rewriteapp {
# rewrite all to app.php
rewrite ^(.*)$ /app.php/$1 last;
}
location ~ ^/(app|app_dev|config)\.php(/|$) {
fastcgi_pass 127.0.0.1:9000;
fastcgi_split_path_info ^(.+\.php)(/.*)$;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param HTTPS off;
}
}
Where:
listen is the port that your application communicate with the world.
fastcgi_pass is a binary protocol for interfacing interactive programs with a web server
References:
Aws ElasticBeanstalk - Nodejs platform proxy
Symfony Hhvm 3 nginx 1.4 vs PHP 5.5 apache 2.4
FastCGI Oficial Example

Resources