I’m trying to deploy a full-stack React/Node.js web app with Letsencrypt to production on an Ubuntu 20.04 LTS server. I’ve built the client and the web page is rendering over https with no problem. The issue arises when I try to make a POST request to the backend.
The React client is running on example.com:3000
.
The Node.js server is running on example.com:9000
.
When I trigger a call to the backend, e.g. example.com:9000/signIn
to get the user’s credentials and sign them in, I get 2 errors in my browser console:
POST https://example.com:9000/signIn net::ERR_SSL_PROTOCOL_ERROR
coming from one of my React components as well as this error: Uncaught (in promise) TypeError: Failed to fetch
.
When I tail the nginx logs, all I see are GET requests loading my front end files/content. Also when I run my node.js server, all I see are logs I left in the application to show that the database is connected successfully. I’m expecting to see some logs indicating whether the user was authenticated or not.
Nginx configuration in /etc/nginx/sites-enabled/example.com
:
server {
root /home/ubuntu/apps/mysite/client/build;
# Add index.php to the list if you are using PHP
index index.html index.htm index.nginx-debian.html;
server_name example.com www.example.com;
location / {
try_files $uri /index.html;
}
location /server {
proxy_pass https://localhost:9000;
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 [::]:443 ssl ipv6only=on; # managed by Certbot
listen 443 ssl; # managed by Certbot
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; # managed by Certbot
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
}
server {
if ($host = www.example.com) {
return 301 https://$host$request_uri;
} # managed by Certbot
if ($host = example.com) {
return 301 https://$host$request_uri;
} # managed by Certbot
listen 80;
listen [::]:80;
server_name example.com www.example.com;
return 404; # managed by Certbot
}
package.json in /server
:
{
"name": "server",
"version": "0.0.0",
"private": true,
"scripts": {
"start": "node ./bin/www"
},
"dependencies": {
"bcrypt": "^4.0.1",
"bcryptjs": "^2.4.3",
"constantinople": "^4.0.1",
"cookie-parser": "~1.4.4",
"cookie-session": "^1.4.0",
"cors": "^2.8.5",
"debug": "~2.6.9",
"dotenv": "^8.2.0",
"express": "~4.16.1",
"express-session": "^1.17.1",
"http-errors": "~1.6.3",
"jade": "~1.11.0",
"morgan": "~1.9.1",
"mysql": "^2.18.1",
"nodemailer": "^6.4.17",
"passport": "^0.4.1",
"passport-http-bearer": "^1.0.1",
"passport-local": "^1.0.0"
}
}
package.json in /client
:
{
"name": "client",
"version": "0.1.0",
"private": true,
"dependencies": {
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.5.0",
"@testing-library/user-event": "^7.2.1",
"bootstrap": "^4.4.1",
"chart.js": "^2.9.3",
"cors": "^2.8.5",
"d3": "^6.2.0",
"moment": "^2.29.1",
"morris.js.so": "^0.5.1",
"node-sass": "^4.14.1",
"perm": "^1.0.0",
"react": "^16.13.1",
"react-bootstrap": "^1.0.1",
"react-chartkick": "^0.4.1",
"react-dom": "^16.13.1",
"react-facebook-login": "^4.1.1",
"react-feather": "^2.0.4",
"react-google-login": "^5.1.10",
"react-router-dom": "^5.2.0",
"react-scripts": "^3.4.3",
"react-timeline-range-slider": "^1.1.2",
"recharts": "^1.8.5",
"universal-cookie": "^4.0.3"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"build-localhost": "PUBLIC_URL=/ react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": "react-app"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"homepage": "https://example.com",
"proxy": "https://example.com:9000",
"devDependencies": {
"dotenv-webpack": "^7.0.2",
"morris.js": "^0.5.0",
"raphael": "^2.3.0"
}
}
Confirming that node.js is in fact listening on port 9000:
bin/www in /server
:
/**
* Get port from environment and store in Express.
*/
var port = normalizePort(process.env.PORT || '9000');
app.set('port', port);
/**
* Create HTTP server.
*/
var server = http.createServer(app);
/**
* Listen on provided port, on all network interfaces.
*/
server.listen(port);
server.on('error', onError);
server.on('listening', onListening);
I should note the entire web app is working locally with no issue. I’ve gone through a number of video tutorials and Stack Overflow answers several times and I’ve confirmed ufw
is configured to allow the necessary ports/traffic and at this point I am out of ideas. Any suggestions on what I’m doing wrong highly appreciated.
3
Answers
1 – Check if your backend is really serving with https
Since you are serving "by your self", on your ubuntu server, you must take care os SSL by your self as well. I see your certbot declaration, but, it’s working?
Testing you production backend URL on some tool like sslshopper will confirm that everything is ok.
2 – Ensure that your client is calling through HTTPS as well
Chrome and other browsers will refuse to, from a HTTPS frontend, call a HTTP backend.
Take a look on the network tab, on DevTools, to see if the request happened or not, and if it returned something as well.
3 – Catch your errors (plus)
The "Uncaught" on your console means you didn’t treated the API call. Try to do some like:
Maybe on that
console.error
log you will could get more information about the real error motive.I don’t know what’s the case in the production server, but I once faced the same connection error between two cloud services and I fixed it by whitelisting their ip addresses in each other connection security.
You are making a HTTPS request to a HTTP backend – on port 9000 you have your backend application on HTTP. Your NGINX configuration is listening on HTTPS on
/server
and does aproxy_pass https://localhost:9000;
hence the request from the UI should look likehttps://example.com/server/signIn
.Also, port 9000 should not be accessible form UI, ideally it should be blocked from firewall and only allow traffic on port 443 (NGINX HTTPS).