Search code examples
node.jsnginxsslstaticsubdomain

Nginx static site w/ SSL + subdomain proxy pass to NodeJS


I'm trying to set up a website with contact form on a VPS (Debian 12). Nnginx serves the static files at domain.com, and the contact form POSTs to form.domain.com, which is handled by a node/express server using nodemailer.

I got all that working in http with Nginx listening on port 80, and doing a proxy_pass to Node at port 3000 for form.domain.com. But then I tried to install ssl using certbot, and it broke the form.

I fixed the proxy_pass issues with http/https and Nginx passing to Node, but that solution means that Node has to use SSL certificates, and that's where I ran into the brick wall of Node being unable to read the the certificate files, which is apparently a known issue.

Basically, my question is: what's the right way to do this?

---What I tried so far---

Before installing SSL through Certbot, while struggling to get the subdomain proxy_pass working, I messed around with the Nginx .conf files a bunch. Initially I tried variations of one server block in nginx.conf, along the lines of:

server {
    listen 80 default_server;
    server_name domain.com;
    location / {
        root /data/www;
        index index.html;
     }
    location /form/ {
        proxy_pass http://012.345.678.99:3000;
  }

Result: No proxy pass... Finally, I gave up on domain.com/form, switched to form.domain.com instead, commented out all server blocks, removed the all links in /etc/nginx/*-enabled/, and added 2 new files in /etc/nginx/conf.d/ -- domain.conf:

server {
    listen 80;
    server_name domain.com;
    location / {
        root /data/www;
        index index.html;
    }
}

-- and form.conf:

server {
    listen 80;
    server_name form.domain.com;
    location / {
        proxy_pass http://012.345.678.99:3000;
    }
}

Result: proxy pass works, node server on port 3000 gets the form data, email works, everything works, yay!!! Just no ssl.

So then I installed snap, installed Certbot, did sudo certbot --nginx and chose to install for domain.com only. Result: https works on domain.com, static site still works, but now submitting the contact form results in an Nginx error (I forget if it was a 404 or a 502 initially).

OK, so long story short(er): I installed SSL for form.domain.com, changed all the http links to https, and somehow got the Nginx proxy_pass working so that the form submission data was getting to Node, but the Node/Express code had to be updated to use the SSL certificates. It seemed like I was stumbling towards a solution solution there, variations along the lines of:

const privateKey = fs.readFileSync('/etc/letsencrypt/live/form.domain.com/privkey.pem', 'utf8');
const certificate = fs.readFileSync('/etc/letsencrypt/live/form.domain.com/cert.pem', 'utf8');
const ca = fs.readFileSync('/etc/letsencrypt/live/form.domain.com/chain.pem', 'utf8');


const credentials = {
        key: privateKey,
        cert: certificate,
        ca: ca
};

const httpServer = http.createServer(app);
const httpsServer = https.createServer(credentials, app);

Result: Node: permission denied.

Apparently this is a known issue with Node ssl on linux. I tried changing permissions as reluctantly suggested in that thread, but no joy.

So how do I fix the node permissions issue? Or, since all I'm trying to do is set up a static website with contact form on a VPS, how should I modify my approach?


Solution

  • I figured out the answer. (The magic line ended up being proxy_ssl_trusted_certificate /etc/letsencrypt/live/domain.com/fullchain.pem -- i.e. a reference to the main domain's certificate within the location section of the subdomain's nginx server block right next to the proxy_pass.)

    In case it helps anyone:

    In this case of sending an Nginx proxy request to a NodeJS app and trying to get the whole thing to work over https rather than http, you don't need to mess with any of the NodeJS code at all. Everything to do with the ssl certificates can be handled by Nginx.

    Starting assumptions:

    • Nginx is serving a static site at domain.com
    • The site has something like a form that needs to POST to a subdomain like form.domain.com
    • Nginx is set up to proxy_pass that form.domain.com request to the NodeJS (or whatever) server listening on an insecure port (like 3000)
    • All this is already set up and working properly over http

    What I did:

    • Installed certbot

    • edited /etc/nginx/sites-available/form.domain.com.conf location block to include the following: proxy_pass https://123.456.789.01:3000;, proxy_ssl_trusted_certificate /path/to/main/domain's/certificate, proxy_set_header Host $host;, proxy_set_header X-Forwarded-Proto $scheme;, proxy_set_header X-Real-IP $remote_addr;, and proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

    • Installed new ssl certificates for both domains using sudo certbot certonly --nginx (I installed the main domain's certificate first, then tested, then installed the subdomain's. I imagine you could do them at the same time, but can't confirm that.)