I deployed 2 applications in a Ubuntu 22.04.4 LTS server,
a backend FastAPI service, and a frontend Reactjs application.
The backend runs on http://localhost:8000,
and it serves APIs at /api/v1
The frontend runs on http://localhost:3000
and can locally reach the backend APIs.
My goal is to access the frontend application from a web.mydomain.com,
having the reactjs application reach the APIs served locally in the same server.
Nginx should act as a reverse-proxy in a scenario like this.
However, something is not correct in my configuration and I am currently facing the following errors when I access the application at http://web.mydomain.com
Access to XMLHttpRequest at 'http://localhost:8000/api/v1/login/access_token' from origin 'http://web.mydomain.com' has been blocked by CORS policy: The request client is not a secure context and the resource is in more-private address space `local`.
node -v
v18.19.1
npm list
── @emotion/cache@11.10.7
├── @emotion/react@11.10.6
├── @emotion/styled@11.10.6
├── @material-ui/core@4.12.4
├── @material-ui/icons@4.11.3
├── @mui/icons-material@5.11.16
├── @mui/joy@5.0.0-beta.19
├── @mui/material@5.12.0
├── @mui/styled-engine@5.12.0
├── @mui/styles@5.15.3
├── @mui/x-charts@6.18.4
├── @mui/x-date-pickers@6.18.6
├── @tanstack/react-query@5.18.1
├── @testing-library/jest-dom@5.16.5
├── @testing-library/react@14.0.0
├── @testing-library/user-event@14.4.3
├── axios@1.6.2
├── chroma-js@2.4.2
├── dayjs@1.11.10
├── dotenv@16.4.4
├── flatpickr@4.6.13
├── http-proxy-middleware@2.0.6
├── material-react-table@2.10.0
├── material-table@2.0.5
├── material-ui-dropzone@3.5.0
├── process@0.11.10
├── prop-types@15.8.1
├── react-copy-to-clipboard@5.1.0
├── react-countup@6.4.2
├── react-dom@18.2.0
├── react-flatpickr@3.10.13
├── react-router-dom@6.10.0
├── react-scripts@5.0.1
├── react-syntax-highlighter@15.5.0
├── react@18.2.0
├── simple-react-lightbox@3.6.9-0
└── web-vitals@3.3.1
Python 3.10.5
nginx -v
nginx version: nginx/1.18.0 (Ubuntu)
My initial and basic configuration was
upstream backend {
server localhost:8000;
}
server {
listen 80;
charset utf-8;
server_name web.mydomain.com;
root /var/www/reactjs_frontend/build;
index index.html index.htm;
include /etc/nginx/mime.types;
gzip on;
gzip_types text/css text/javascript application/x-javascript application/json;
# backend urls
location ~ ^/(admin|api|media) {
proxy_redirect off;
proxy_pass http://backend;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
}
# static files
location /static {
alias /var/www/reactjs_frontend/build/static/;
}
# frontend
location / {
try_files $uri $uri/ /index.html;
}
}
The application is loaded at http://web.mydomain.com,
but when I try to access the backend's API, let's say http://localhost:8000/api/v1/login/access_token
as a result of filling the login form, the following issue occurs:
Access to XMLHttpRequest at 'http://localhost:8000/api/v1/login/access_token' from origin 'http://web.mydomain.com' has been blocked by CORS policy: The request client is not a secure context and the resource is in more-private address space `local`.
After browsing through a few similar issues, such as
and a few more, I changed my configuration, applying the following changes, but without any luck. The issue is still occurring.
This is my current configuration:
upstream backend {
server localhost:8000;
}
server {
listen *:80;
server_name web.mydomain.com;
root /var/www/reactjs_frontend/build;
index index.html index.htm;
try_files $uri /index.html;
access_log /var/log/nginx/reactjs_access.log;
error_log /var/log/nginx/reactjs_error.log;
location / {
add_header 'Access-Control-Allow-Origin' '*' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'Accept,Authorization,Cache-Control,Content-Type,DNT,If-Modified-Since,Keep-Alive,Origin,User-Agent,X-Requested-With' always;
add_header 'Access-Control-Expose-Headers' 'Access-Control-Allow-Origin';
add_header 'Access-Control-Allow-Credentials' 'true' always;
add_header 'Access-Control-Request-Private-Network' 'true';
add_header 'Access-Control-Allow-Private-Networl' 'true';
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;
if ($request_method = 'OPTIONS' ) {
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'application/json;';
return 204;
}
if ($request_method = 'GET') {
add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
}
if ($request_method = 'POST') {
add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
}
proxy_pass_request_headers on;
}
location /api/v1/ {
proxy_pass http://localhost:8000/api/v1/;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header authorization $http_authorization;
proxy_pass_request_headers on;
proxy_no_cache $cookie_nocache $arg_nocache$arg_comment;
proxy_no_cache $http_pragma $http_authorization;
proxy_cache_bypass $cookie_nocache $arg_nocache $arg_comment;
if ($request_method = 'OPTIONS') {
#
# Tell client that this pre-flight info is valid for 20 days
#
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Access-Control-Allow-Origin' 'http://localhost:3000' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, HEAD' always;
add_header 'Access-Control-Allow-Headers' 'authorization, Origin, X-Requested-With, Content-Type, Accept' always;
add_header 'Access-Control-Allow-Credentials' 'true' always;
return 204;
}
if ($request_method = 'POST') {
add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
add_header 'Access-Control-Allow-Origin' 'http://localhost:3000' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, HEAD' always;
add_header 'Access-Control-Allow-Headers' 'authorization, Origin, X-Requested-With, Content-Type, Accept' always;
add_header 'Access-Control-Allow-Credentials' 'true' always;
}
if ($request_method = 'GET') {
add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
add_header 'Access-Control-Allow-Origin' 'http://localhost:3000' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, HEAD' always;
add_header 'Access-Control-Allow-Headers' 'authorization, Origin, X-Requested-With, Content-Type, Accept' always;
add_header 'Access-Control-Allow-Credentials' 'true' always;
}
}
}
To add more info about the context, this is the index.html
generated by rpm run build
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="shortcut icon" href="/favicon.png"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#1A73E8"/><link rel="apple-touch-icon" sizes="76x76" href="/apple-icon.png"/><link rel="manifest" href="/manifest.json"/><title>MyApp</title><link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700,900|Roboto+Slab:400,700"/><link href="https://fonts.googleapis.com/css?family=Material+Icons|Material+Icons+Outlined|Material+Icons+Two+Tone|Material+Icons+Round|Material+Icons+Sharp" rel="stylesheet"/><script src="https://kit.fontawesome.com/42d5adcbca.js" crossorigin="anonymous"></script><script defer="defer" data-site="YOUR_DOMAIN_HERE" src="https://api.nepcha.com/js/nepcha-analytics.js"></script><script defer="defer" src="/static/js/main.13416a3b.js"></script><link href="/static/css/main.be921a39.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
I have also tried the following very common configuration
server {
listen 80;
root /var/www/reactjs_frontend/build;
index index.html index.htm index.nginx-debian.html;
server_name web.mydomain.com;
location / {
# serve static frontend first
try_files $uri $uri/ /index.html =404;
}
# location ~*^/(api|posts|products) {
location /api {
if ($request_method = OPTION) {
add_header Access-Control-Allow-Origin web.mydomain.com;
add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
add_header Content-Type text/plain;
add_header Content-Length 0;
return 204;
}
add_header Access-Control-Allow-Origin web.mydomain.com;
add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
proxy_pass http://localhost:8000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header Connection keep-alive;
proxy_cache_bypass $http_upgrade;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
without any luck.
I have also add to the reactjs application the following http-proxy-middleware
in the webpack.config.js
const {createProxyMiddleware} = require('http-proxy-middleware');
module.exports = {
resolve: {
fallback: { process: require.resolve('process/browser') },
},
plugins: [
new createProxyMiddleware({
target:"http://localhost:8000",
changeOrigin: true,
})
],
};
As for this clear explanation, the problem was not in the configuration.
Instead of trying to have the react app reach the backend running on localhost, I have to access it available from http://web.mydomain.com/api/v1/
The following configuration exposes the backend on http://web.mydomain.com/api/v1/
The reactjs now, instead of using http://localhost:8000
, it uses http://web.mydomain.com/api/v1/
and everything works as expected.
server {
listen 80;
root /var/www/reactjs_frontend/build;
index index.html index.htm index.nginx-debian.html;
server_name web.mydomain.com;
location / {
# serve static frontend first
try_files $uri $uri/ /index.html =404;
}
location /api/v1/ {
add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS, PATCH, DELETE';
proxy_pass http://localhost:8000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header Connection keep-alive;
proxy_cache_bypass $http_upgrade;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_redirect off;
}
}
Reverse-proxy receives requests from clients and forwards them to the appropriate application, but it only works when you are actually hitting the endpoint. In your case, you are trying to hit a local endpoint from your remote frontend application which is the main issue.
Your frontend and backend are hosted on the same server and your subdomain web.mydomain.com
is linked to the server. As per your nginx config, path /
serves your frontend and /api/v1/*
serves your backend. This is known as path-based routing. When you hit https://web.mydomain.com/
, your reverse-proxy (nginx in this case) knows that it has to serve the frontend application and it provides the necessary files (HTML, CSS, JS, and other assets). Now onwards, everything that's happening in the web app, is taking place in the on your browser only. So, when your web application hits http://localhost:8000/api/v1/
from your browser, it is trying to access the localhost of that machine and not the one from the server.
To access the backend, you need to hit https://web.mydomain.com/api/v1/*
.
The error you got indicates that you are trying to access private network resources which is blocked by most of the browsers by default. Similar issue you can find here as well.
Hope this helps :)