2

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.

7
  • Had you considered going serverless? The entire node application can be a single Lambda function. Commented Apr 29, 2021 at 3:13
  • Is this a development environment? Because in production, you don't need to run anything except of the NodeJS server to have your app running. What do you have on the 3000 port running? If you have Node server running on port 9000 then you just proxy pass Nginx there as you're doing and then you just use the domain without a port https://example.com/signIn. Commented Apr 29, 2021 at 8:02
  • @ChristosLytras this is a production environment; I have built the React app being served on port 3000. If I understand correctly, are you suggesting I try the approach here? Commented May 1, 2021 at 12:43
  • @brad you don't need a static server when you have a web server like Nginx. What do you mean exactly when you say "the React app being served on port 3000"? Being served by what and why? What do you run and bind to the port 3000? When building for production, all the files are compiled down to Javascript and you don't need anything to start any runtime environment for the client to run like yarn run start we're doing for development. Commented May 1, 2021 at 13:30
  • @ChristosLytras okay my mistake-- nothing is actually running on port 3000. I updated the calls to the backend to use https://example.com/signIn instead of https://example.com:9000/signIn. I then got the following error: 405 error: not allowed. I looked at this answer then updated my nginx config to rewrite the 405 to be 200. The response from the server is now 'You need to enable JavaScript to run this app.' instead of the JSON response I expect to get. Commented May 1, 2021 at 16:00

3 Answers 3

1

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:

try{
   // call your API
} catch (error){
  console.error(error);
  // update your local state telling your user that something went wrong
}

Maybe on that console.error log you will could get more information about the real error motive.

Sign up to request clarification or add additional context in comments.

Comments

0

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.

Comments

0
+50
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.

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 a proxy_pass https://localhost:9000; hence the request from the UI should look like https://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).

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.