I’m trying to configure an NGINX reverse proxy to forward requests to a Docker container running a NestJS application on port 3000. While HTTP requests work fine, HTTPS requests return a 404 Not Found
error.
I’m using Plesk with multiple domains on a single IP address. Below are the configurations:
/etc/nginx/nginx.conf
#user nginx;
worker_processes 1;
#error_log /var/log/nginx/error.log;
#error_log /var/log/nginx/error.log notice;
#error_log /var/log/nginx/error.log info;
#pid /var/run/nginx.pid;
include /etc/nginx/modules.conf.d/*.conf;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
#log_format main '$remote_addr - $remote_user [$time_local] "$request" '
# '$status $body_bytes_sent "$http_referer" '
# '"$http_user_agent" "$http_x_forwarded_for"';
#access_log /var/log/nginx/access.log main;
sendfile on;
#tcp_nopush on;
#keepalive_timeout 0;
keepalive_timeout 65;
#tcp_nodelay on;
#gzip on;
#gzip_disable "MSIE [1-6].(?!.*SV1)";
server_tokens off;
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*.conf;
}
# override global parameters e.g. worker_rlimit_nofile
include /etc/nginx/*global_params;
/etc/nginx/sites-available/my_domain.conf
server {
listen 80;
server_name my_domain.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name my_domain.com;
ssl_certificate /etc/ssl/certs/my_domain.com.crt;
ssl_certificate_key /etc/ssl/private/my_domain.com.key;
ssl_dhparam /opt/psa/etc/dhparams2048.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers EECDH+AESGCM+AES128:EECDH+AESGCM+AES256:EECDH+CHACHA20:EECDH+SHA256+AES128:EECDH+SHA384+AES256:EECDH+SHA1+AES128:EECDH+SHA1+AES256:EECDH+HIGH:AESGCM+AES128:AESGCM+AES256:CHACHA20:SHA256+A>;
ssl_prefer_server_ciphers on;
location / {
proxy_pass http://localhost:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Docker Containers
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
96bc630cc7c4 docker_my_app "docker-entrypoint.s…" 7 weeks ago Up 2 days 0.0.0.0:3000->3000/tcp, :::3000->3000/tcp docker_my_app_1
663a96e88a53 bitnami/redis:5.0 "/opt/bitnami/script…" 7 weeks ago Up 2 days 6379/tcp docker_redisdb_1
581ff6d6380c postgis/postgis "docker-entrypoint.s…" 7 weeks ago Up 2 days 5432/tcp docker_postgres_1
SSL Certificate Generation
# Step 1: Generate a Private Key
openssl genpkey -algorithm RSA -out /etc/ssl/private/my_domain.com.key -aes256
# Step 2: Generate a Certificate Signing Request (CSR)
openssl req -new -key /etc/ssl/private/my_domain.com.key -out /etc/ssl/certs/my_domain.com.csr
# Step 3: Generate a Self-Signed Certificate
openssl x509 -req -days 365 -in /etc/ssl/certs/my_domain.com.csr -signkey /etc/ssl/private/my_domain.com.key -out /etc/ssl/certs/my_domain.com.crt
curl
Results
curl -v localhost:3000/api/articles
* Trying 127.0.0.1:3000...
* Connected to localhost (127.0.0.1) port 3000 (#0)
> GET /api/articles HTTP/1.1
> Host: localhost:3000
> User-Agent: curl/7.81.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
...
<
curl -v http://my_domain.com:3000/api/articles
* Trying IP_ADDRESS:3000...
* Connected to my_domain.com (IP_ADDRESS) port 3000
> GET /api/articles HTTP/1.1
> Host: my_domain.com:3000
> User-Agent: curl/8.4.0
> Accept: */*
>
< HTTP/1.1 200 OK
...
<
curl -v https://my_domain.com/api/articles
* Trying IP_ADDRESS:443...
* Connected to my_domain.com (IP_ADDRESS) port 443
* ALPN: curl offers h2,http/1.1
* (304) (OUT), TLS handshake, Client hello (1):
* CAfile: /etc/ssl/cert.pem
* CApath: none
* (304) (IN), TLS handshake, Server hello (2):
* (304) (IN), TLS handshake, Unknown (8):
* (304) (IN), TLS handshake, Certificate (11):
* (304) (IN), TLS handshake, CERT verify (15):
* (304) (IN), TLS handshake, Finished (20):
* (304) (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / AEAD-AES256-GCM-SHA384
* ALPN: server accepted h2
* Server certificate:
* subject: CN=my_domain.com
* start date: May 24 10:52:28 2024 GMT
* expire date: Aug 22 10:52:27 2024 GMT
* subjectAltName: host "my_domain.com" matched cert's "my_domain.com"
* issuer: C=US; O=Let's Encrypt; CN=R3
* SSL certificate verify ok.
* using HTTP/2
* [HTTP/2] [1] OPENED stream for https://my_domain.com/api/articles
* [HTTP/2] [1] [:method: GET]
* [HTTP/2] [1] [:scheme: https]
* [HTTP/2] [1] [:authority: my_domain.com]
* [HTTP/2] [1] [:path: /api/articles]
* [HTTP/2] [1] [user-agent: curl/8.4.0]
* [HTTP/2] [1] [accept: */*]
> GET /api/articles HTTP/2
> Host: my_domain.com
> User-Agent: curl/8.4.0
> Accept: */*
>
< HTTP/2 404
< server: nginx
< date: Sun, 26 May 2024 09:35:18 GMT
< content-type: text/html
< content-length: 808
< last-modified: Sat, 06 Apr 2024 23:52:19 GMT
< etag: "328-6157643499620"
< accept-ranges: bytes
<
It appears that the server uses a Let’s Encrypt certificate generated from the Plesk dashboard (still valid, created recently), and while HTTP redirects to HTTPS correctly, HTTPS requests return 404 NOT FOUND
. The application on port 3000 is accessible remotely.
Can anyone help identify what might be causing the 404 Not Found
error for HTTPS requests?