Skip to content
Go back

Local Node.js App with HTTPS on Ubuntu 22.04 Using Self-Signed SSL, Nginx & Apache Reverse Proxy

Published:

output

output


Quick 1-paragraph summary (follow this if you want just the steps)

  1. Create cert+key with OpenSSL (with SAN), store as /etc/ssl/private/nodejs.janakkumarshrestha0.com.np.key and /etc/ssl/certs/nodejs.janakkumarshrestha0.com.np.crt.
  2. Add 127.0.0.1 nodejs.janakkumarshrestha0.com.np to /etc/hosts.
  3. Configure Nginx (or Apache) to listen on HTTPS 443, using the cert and reverse proxying to http://127.0.0.1:3000. Enable and reload service.
  4. Optionally add the cert to OS trust (/usr/local/share/ca-certificates/... + sudo update-ca-certificates) or use mkcert to generate trusted local certs. (Full commands & file contents below with explanations.)

Environment assumptions


1 — Create a self-signed certificate (with Subject Alternative Name)

Modern browsers require SAN (Subject Alternative Name). We’ll create a small OpenSSL config file that includes SAN and then generate a 2048-bit key + certificate valid for 1 year.

  1. Create a temp openssl config file ~/nodejs-openssl.cnf with SAN:
cat > ~/nodejs-openssl.cnf <<'EOF'
[ req ]
default_bits       = 2048
prompt             = no
default_md         = sha256
req_extensions     = req_ext
distinguished_name = dn

[ dn ]
C  = NP
ST = Bagmati
L  = Kathmandu
O  = Personal
CN = nodejs.janakkumarshrestha0.com.np

[ req_ext ]
subjectAltName = @alt_names

[ alt_names ]
DNS.1 = nodejs.janakkumarshrestha0.com.np
DNS.2 = localhost
IP.1  = 127.0.0.1

[ v3_ext ]
authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage = digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth
subjectAltName = @alt_names
EOF

Explanation:

  1. Generate private key and certificate (valid 365 days):
sudo openssl req -x509 -nodes -days 365 \
  -newkey rsa:2048 \
  -keyout /tmp/nodejs.janakkumarshrestha0.com.np.key \
  -out /tmp/nodejs.janakkumarshrestha0.com.np.crt \
  -config ~/nodejs-openssl.cnf \
  -extensions v3_ext

Explanation:


2 — Store cert and key in safe paths with correct permissions

We’ll move certs to canonical locations and set safe permissions.

  1. Create directories (if not exist) and move files:
sudo mkdir -p /etc/ssl/private
sudo mkdir -p /etc/ssl/certs
sudo mv /tmp/nodejs.janakkumarshrestha0.com.np.key /tmp/nodejs.janakkumarshrestha0.com.np.crt /etc/ssl/private/
# Move certificate to certs for clarity:
sudo mv /etc/ssl/private/nodejs.janakkumarshrestha0.com.np.crt /etc/ssl/certs/nodejs.janakkumarshrestha0.com.np.crt
sudo mv /etc/ssl/private/nodejs.janakkumarshrestha0.com.np.key /etc/ssl/private/nodejs.janakkumarshrestha0.com.np.key

Note: we moved the certificate to /etc/ssl/certs and kept the key in /etc/ssl/private.

  1. Set permissions:
# Certificate readable by everyone
sudo chmod 644 /etc/ssl/certs/nodejs.janakkumarshrestha0.com.np.crt
sudo chown root:root /etc/ssl/certs/nodejs.janakkumarshrestha0.com.np.crt

# Private key only readable by root (600)
sudo chmod 600 /etc/ssl/private/nodejs.janakkumarshrestha0.com.np.key
sudo chown root:root /etc/ssl/private/nodejs.janakkumarshrestha0.com.np.key

Explanation:


3 — Make nodejs.janakkumarshrestha0.com.np resolve locally

Edit /etc/hosts so that the hostname resolves to your machine (loopback). Add this single exact line.

  1. Add the line:
# Backup hosts first
sudo cp /etc/hosts /etc/hosts.bak

# Add mapping (this is the exact line to add)
echo '127.0.0.1 nodejs.janakkumarshrestha0.com.np' | sudo tee -a /etc/hosts

Exact line to add (copy/paste into /etc/hosts if you prefer a text editor):

127.0.0.1 nodejs.janakkumarshrestha0.com.np

Explanation:


4 — Full Nginx configuration (HTTPS reverse proxy to localhost:3000)

We’ll create a server block file, enable it, test, and reload.

  1. Install Nginx if you don’t have it:
sudo apt update
sudo apt install -y nginx
  1. Create Nginx site file at /etc/nginx/sites-available/nodejs.janakkumarshrestha0.com.np:
sudo tee /etc/nginx/sites-available/nodejs.janakkumarshrestha0.com.np > /dev/null <<'EOF'
# /etc/nginx/sites-available/nodejs.janakkumarshrestha0.com.np
server {
    # Listen on IPv4 and IPv6, HTTPS
    listen 443 ssl;
    listen [::]:443 ssl;

    # Your hostname
    server_name nodejs.janakkumarshrestha0.com.np;

    # Paths to certificate and private key
    ssl_certificate     /etc/ssl/certs/nodejs.janakkumarshrestha0.com.np.crt;
    ssl_certificate_key /etc/ssl/private/nodejs.janakkumarshrestha0.com.np.key;

    # SSL settings (basic, modern safe defaults)
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers on;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 10m;

    # Proxy settings
    location / {
        proxy_pass         http://127.0.0.1:3000/;
        proxy_http_version 1.1;

        # Preserve the original Host header (so your app sees requested host)
        proxy_set_header   Host $host;

        # Forward real client IPs
        proxy_set_header   X-Real-IP $remote_addr;
        proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;

        # Indicate the original protocol used by client
        proxy_set_header   X-Forwarded-Proto $scheme;

        # WebSocket support
        proxy_set_header   Upgrade $http_upgrade;
        proxy_set_header   Connection $connection_upgrade;
    }
}

# Optional HTTP -> HTTPS redirect
server {
    listen 80;
    listen [::]:80;
    server_name nodejs.janakkumarshrestha0.com.np;

    return 301 https://$host$request_uri;
}
EOF

Explanation of important directives:

  1. Create the $connection_upgrade variable so Connection header is correct (some nginx versions require):

Append this to file above http block? If using default Ubuntu nginx, you can set inside the server block — but simpler: add this at top of the same file (just before first server {):

We already used $connection_upgrade — some nginx configurations require setting:

# (Only if you get "unknown variable $connection_upgrade" errors)
# Add the following line near the top of /etc/nginx/nginx.conf within 'http { ... }'
# Or you can add a fast workaround at top of this site file:
sudo sed -n '1,1p' /etc/nginx/sites-available/nodejs.janakkumarshrestha0.com.np >/dev/null || true

(Usually modern nginx supports $connection_upgrade mapping via map in global nginx.conf. If you see errors, use the troubleshooting section below.)

🧱 Fixing the “unknown $connection_upgrade variable” Error

If you see:

unknown "connection_upgrade" variable

edit /etc/nginx/nginx.conf and inside the http { ... } block (before any server {}) add:

map $http_upgrade $connection_upgrade {
    default upgrade;
    '' close;
}
  1. Enable site and reload Nginx:
sudo ln -s /etc/nginx/sites-available/nodejs.janakkumarshrestha0.com.np /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

Explanation:

  1. Permissions (already discussed): ensure /etc/ssl/private/...key is 600 root:root so only root reads it.

5 — Full Apache configuration (HTTPS reverse proxy)

If you prefer Apache instead of Nginx, here’s a complete Apache VirtualHost.

  1. Install Apache if you don’t have it:
sudo apt update
sudo apt install -y apache2
  1. Enable required Apache modules:
sudo a2enmod ssl
sudo a2enmod proxy
sudo a2enmod proxy_http
sudo a2enmod headers
sudo a2enmod remoteip

Explanation:

  1. Create the Apache site file /etc/apache2/sites-available/nodejs.janakkumarshrestha0.com.np.conf with full content:
sudo tee /etc/apache2/sites-available/nodejs.janakkumarshrestha0.com.np.conf > /dev/null <<'EOF'
<VirtualHost *:443>
    ServerName nodejs.janakkumarshrestha0.com.np

    # Path to the certificate and key
    SSLEngine on
    SSLCertificateFile    /etc/ssl/certs/nodejs.janakkumarshrestha0.com.np.crt
    SSLCertificateKeyFile /etc/ssl/private/nodejs.janakkumarshrestha0.com.np.key

    # Optional SSL settings for stronger security
    SSLProtocol             all -SSLv3 -TLSv1 -TLSv1.1
    SSLOptions              +StrictRequire
    # SSLHonorCipherOrder     on
    # SSLCipherSuite         HIGH:!aNULL:!MD5

    # Preserve original host header
    ProxyPreserveHost On

    # Proxy pass everything to the Node app
    ProxyPass        / http://127.0.0.1:3000/
    ProxyPassReverse / http://127.0.0.1:3000/

    # Forward client IP to backend
    RequestHeader set X-Forwarded-Proto "https"
    RequestHeader set X-Forwarded-Port "443"

    # Logging (optional)
    ErrorLog ${APACHE_LOG_DIR}/nodejs-error.log
    CustomLog ${APACHE_LOG_DIR}/nodejs-access.log combined
</VirtualHost>

# Optional: Redirect HTTP to HTTPS
<VirtualHost *:80>
    ServerName nodejs.janakkumarshrestha0.com.np
    Redirect permanent / https://nodejs.janakkumarshrestha0.com.np/
</VirtualHost>
EOF

Explanation:

  1. Enable site and reload Apache:
sudo a2ensite nodejs.janakkumarshrestha0.com.np.conf
sudo apache2ctl configtest
sudo systemctl reload apache2

Explanation:


6 — Testing the setup

6.1 Basic curl (browser & CLI)

Because you added /etc/hosts, the name resolves to 127.0.0.1.

  1. Quick insecure test with curl (ignores TLS trust checks):
curl -v --insecure https://nodejs.janakkumarshrestha0.com.np/

Explanation:

  1. Test without --insecure (after you add cert to system trust — explained below):
curl -v https://nodejs.janakkumarshrestha0.com.np/

If trust is not added yet, curl will report SSL: certificate subject name or self signed certificate errors.

6.2 Check certificate with openssl s_client

openssl s_client -connect nodejs.janakkumarshrestha0.com.np:443 -servername nodejs.janakkumarshrestha0.com.np

Explanation:

6.3 Browser behavior & trusting the cert

Quick local trust (systemwide, Ubuntu)

Add the certificate as a trusted CA certificate (works for most system apps and Chrome/Chromium; Firefox uses its NSS store—instructions below):

  1. Copy cert to local CA folder and update trust:
sudo cp /etc/ssl/certs/nodejs.janakkumarshrestha0.com.np.crt /usr/local/share/ca-certificates/nodejs.janakkumarshrestha0.com.np.crt
sudo update-ca-certificates

Explanation:

  1. Restart browser (Chrome/Chromium) and try visiting https://nodejs.janakkumarshrestha0.com.np/ — Chrome should accept it.

Firefox (uses its own NSS DB)

Firefox does not use the system trust store by default. Use certutil (from libnss3-tools) to add cert to your Firefox profile:

  1. Install libnss3-tools if not present:
sudo apt update
sudo apt install -y libnss3-tools
  1. Find your Firefox profile (example command):
ls ~/.mozilla/firefox/*.default-release || ls ~/.mozilla/firefox/*.default || true

Pick the profile folder returned, for example: ~/.mozilla/firefox/abcd1234.default-release.

  1. Import the certificate into that profile’s cert DB:
# Replace PROFILE_DIR with the actual directory returned above
PROFILE_DIR=$(ls -d ~/.mozilla/firefox/*.default-release 2>/dev/null || ls -d ~/.mozilla/firefox/*.default 2>/dev/null)
certutil -d sql:$PROFILE_DIR -A -t "TCu,Cu,Tu" -n "Local nodejs cert" -i /etc/ssl/certs/nodejs.janakkumarshrestha0.com.np.crt

Explanation:

mkcert creates a local CA and automatically trusts it in system/NSS stores, making locally-issued certs trusted by browsers without warnings.

Install mkcert and use:

# Install prerequisites and mkcert (typical approach)
sudo apt update
sudo apt install -y libnss3-tools
# Install mkcert via snap (snap is commonly available)
sudo snap install mkcert --classic

# Create and install a local CA (only once)
mkcert -install

# Generate certs for the hostname and localhost
mkcert nodejs.janakkumarshrestha0.com.np localhost 127.0.0.1 ::1

# mkcert produces files like:
# nodejs.janakkumarshrestha0.com.np+2.pem  (cert)
# nodejs.janakkumarshrestha0.com.np+2-key.pem (key)
# Move them to /etc/ssl if desired:
sudo mv nodejs.janakkumarshrestha0.com.np+2.pem /etc/ssl/certs/nodejs.janakkumarshrestha0.com.np.crt
sudo mv nodejs.janakkumarshrestha0.com.np+2-key.pem /etc/ssl/private/nodejs.janakkumarshrestha0.com.np.key
sudo chmod 600 /etc/ssl/private/nodejs.janakkumarshrestha0.com.np.key
sudo chmod 644 /etc/ssl/certs/nodejs.janakkumarshrestha0.com.np.crt

Explanation:


7 — Troubleshooting tips (common problems & fixes)

1 nginx -t fails / nginx refuses to reload

2 apache2ctl configtest fails

3 Port conflicts (another service listening on 443)

4 Firewall (ufw) blocking connections

5 SELinux

6 unknown variable $connection_upgrade or WebSocket issues

7 Backend logs — 502 / 504 errors


8 — Security note (self-signed certs vs production)


9 — One-page checklist / step order you can run

  1. Create the openssl config ~/nodejs-openssl.cnf (copy from step 1).
  2. Run the openssl req -x509 ... command to create cert & key (step 1).
  3. Move cert/key to /etc/ssl/certs and /etc/ssl/private and set permissions (step 2).
  4. Add 127.0.0.1 nodejs.janakkumarshrestha0.com.np to /etc/hosts (step 3).
  5. Install Nginx or Apache and create the config file(s) exactly as shown (steps 4 & 5).
  6. Enable site and reload nginx or apache2 and test with curl and openssl s_client (step 6).
  7. Optionally add cert to system trust (/usr/local/share/ca-certificates/... + sudo update-ca-certificates) or use mkcert.
  8. Troubleshoot using the commands in step 7.

Appendix — exact files & commands in one place (copy/paste friendly)

OpenSSL config (create once)

cat > ~/nodejs-openssl.cnf <<'EOF'
[ req ]
default_bits       = 2048
prompt             = no
default_md         = sha256
req_extensions     = req_ext
distinguished_name = dn

[ dn ]
C  = NP
ST = Bagmati
L  = Kathmandu
O  = Personal
CN = nodejs.janakkumarshrestha0.com.np

[ req_ext ]
subjectAltName = @alt_names

[ alt_names ]
DNS.1 = nodejs.janakkumarshrestha0.com.np
DNS.2 = localhost
IP.1  = 127.0.0.1

[ v3_ext ]
authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage = digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth
subjectAltName = @alt_names
EOF

Generate cert & key

sudo openssl req -x509 -nodes -days 365 \
  -newkey rsa:2048 \
  -keyout /tmp/nodejs.janakkumarshrestha0.com.np.key \
  -out /tmp/nodejs.janakkumarshrestha0.com.np.crt \
  -config ~/nodejs-openssl.cnf \
  -extensions v3_ext

Move to final paths + permissions

sudo mkdir -p /etc/ssl/private /etc/ssl/certs
sudo mv /tmp/nodejs.janakkumarshrestha0.com.np.crt /etc/ssl/certs/nodejs.janakkumarshrestha0.com.np.crt
sudo mv /tmp/nodejs.janakkumarshrestha0.com.np.key /etc/ssl/private/nodejs.janakkumarshrestha0.com.np.key
sudo chown root:root /etc/ssl/certs/nodejs.janakkumarshrestha0.com.np.crt /etc/ssl/private/nodejs.janakkumarshrestha0.com.np.key
sudo chmod 644 /etc/ssl/certs/nodejs.janakkumarshrestha0.com.np.crt
sudo chmod 600 /etc/ssl/private/nodejs.janakkumarshrestha0.com.np.key

/etc/hosts (exact line)

127.0.0.1 nodejs.janakkumarshrestha0.com.np

Add it with:

sudo cp /etc/hosts /etc/hosts.bak
echo '127.0.0.1 nodejs.janakkumarshrestha0.com.np' | sudo tee -a /etc/hosts

Full Nginx file (exact path)

/etc/nginx/sites-available/nodejs.janakkumarshrestha0.com.np (Use the same content as shown previously in Step 4; you can create it with:)

sudo tee /etc/nginx/sites-available/nodejs.janakkumarshrestha0.com.np > /dev/null <<'EOF'
# (paste the Nginx server block content from earlier)
EOF

Enable + reload:

sudo ln -s /etc/nginx/sites-available/nodejs.janakkumarshrestha0.com.np /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

Full Apache file (exact path)

/etc/apache2/sites-available/nodejs.janakkumarshrestha0.com.np.conf (Use the same content as shown previously in Step 5; create with:)

sudo tee /etc/apache2/sites-available/nodejs.janakkumarshrestha0.com.np.conf > /dev/null <<'EOF'
# (paste the Apache VirtualHost content from earlier)
EOF

Enable and reload Apache:

sudo a2enmod ssl proxy proxy_http headers
sudo a2ensite nodejs.janakkumarshrestha0.com.np.conf
sudo apache2ctl configtest
sudo systemctl reload apache2

Quick tests

# Test HTTPS with curl (insecure)
curl -v --insecure https://nodejs.janakkumarshrestha0.com.np/

# Test HTTPS with openssl (SNI)
openssl s_client -connect nodejs.janakkumarshrestha0.com.np:443 -servername nodejs.janakkumarshrestha0.com.np

Add cert to system trust (Ubuntu)

sudo cp /etc/ssl/certs/nodejs.janakkumarshrestha0.com.np.crt /usr/local/share/ca-certificates/nodejs.janakkumarshrestha0.com.np.crt
sudo update-ca-certificates
# Restart browser after this
sudo apt update
sudo apt install -y libnss3-tools
sudo snap install mkcert --classic
mkcert -install
mkcert nodejs.janakkumarshrestha0.com.np localhost 127.0.0.1 ::1
# Move mkcert outputs to /etc/ssl paths if desired, then set permissions

🧭 One-Paragraph Summary

To enable HTTPS for a local Node.js app on Ubuntu 22.04: map your custom domain in /etc/hosts, generate a self-signed certificate with OpenSSL, store it under /etc/ssl/, configure Nginx or Apache as a reverse proxy with SSL, test using curl -vk, and add the certificate to your local trust store (or use mkcert). For production, replace the self-signed certificate with a Let’s Encrypt-issued one.


Suggest Changes

Previous Post
Essential GNU Nano Text Editor Key Bindings
Next Post
Essential Linux Terminal Key Bindings for Efficient Command Line Editing