My issue:
We are running a Laravel application inside a Docker container using NGINX and PHP-FPM. Since port 443 is already in use on our local development machines, we map host port 8433 to container port 443, and host port 8021 to container port 80.
We have two main issues with port forwarding and redirects:
-
When NGINX redirects HTTP to HTTPS via return 301 https://$host$request_uri;, it drops the port number, redirecting http://localhost:8021 to https://localhost/login (on port 443, which is closed), resulting in a “Connection Refused” error.
-
Inside the PHP block, we hardcode fastcgi_param SERVER_PORT 443; because, in our production environment, the site runs behind an SSL Passthrough proxy that routes public traffic from https://domain.com (port 443) to our container’s alternative port (8433). However, locally on port 8433, this causes Laravel to generate all assets and redirects without the port, breaking the CSS/JS and page redirects.
We are looking for a way to configure NGINX to dynamically handle redirects and pass the correct external port to PHP-FPM when running behind a port-forwarded Docker container, so that it works seamlessly both locally (on https://localhost:8433) and in production (on https://domain.com without a port).
How I encountered the problem:
We set up a Docker Compose environment mapping 8021:80 and 8433:443. When trying to access http://localhost:8021 or https://localhost:8433, the browser is redirected to https://localhost/login (dropping the port) and fails with a connection error.
Solutions I’ve tried:
-
We tried forcing Laravel’s base URL using URL::forceRootUrl in AppServiceProvider based on the .env APP_URL variable. This works on one developer’s machine but fails on another colleague’s machine (getting connection refused), possibly due to Docker/WSL2 network caching or NGINX and FastCGI parameter overrides.
-
Using port 443 on host is not an option as it is already allocated on local machines.
Version of NGINX or NGINX adjacent software (e.g. NGINX Gateway Fabric):
NGINX 1.22+ (running inside official Debian-based PHP 8.3-FPM Docker image).
Deployment environment:
Docker Desktop on Windows 10/11 using the WSL2 backend.
Minimal NGINX config to reproduce your issue:
server {
listen 80;
server_name \_;
\# Drops the mapped port (8021) and redirects to default 443
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name \_;
root /var/www/html/public;
index index.php index.html;
ssl_certificate /etc/nginx/certs/cert.pem;
ssl_certificate_key /etc/nginx/certs/server-key.pem;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location \~ \\.php$ {
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param HTTPS on;
\# Hardcoded to 443 for production, but breaks local alternative ports
fastcgi_param SERVER_PORT 443;
}
}
NGINX access/error log:
127.0.0.1 - - [26/May/2026:18:09:18 +0000] "GET /index.php HTTP/1.1" 302 0 "-" "Mozilla/5.0..."
Hello Daniel,
Regarding this issue, I have a few questions to better understand your situation:
- is your website hosted locally on your machine? Or do you have a dedicated server (or VM or container) for this? To stay it clearly, are these redirections to
localhost expected to be working or was it never the intent?
- Have you already considered SNI routing in your architecture? Would this be applicable? From my understanding, your port forwarding trick stems from the fact that you have multiple services listening on the port 443 or your server, but this can be solved in HTTPS with SNI routing, natively integrated in NGINX (Configuring HTTPS servers → section Server Name Indication).
Answers to the above would be very helpful to propose the best solution, but from your description here’s what I’d already recommend:
- For your redirection issue, I’d understand that it comes from the
$host variable definition (Module ngx_http_core_module). This one does not necessarily include the port. For this you may try just replacing return 301 https://$host$request_uri; by return 301 https://$host:8443$request_uri;. Also, to ease your development, maybe try using code 302 instead of 301 at first, because otherwise you’ll have to clear your 301 redirect cache from your browser after every trial, otherwise you’ll cache the wrong redirect (example of 301 cache clear in firefox: https://superuser.com/questions/467999/how-to-clear-the-301-redirect-cache-in-firefox)
- For this kind of issue, unfortunately I would not directly see another solution than using the
sub_filter module (Module ngx_http_sub_module) to rewrite the Laravel’s returned content and replace the port in the URL with something like sub_filter https://$host https://$host:8443. This is not ideal but if it’s only for development on your local machine why not.
Let me know if this helps. Cheers.
Hello!
Thank you so much for the detailed feedback and the great tips! Quick update: our infrastructure lead and my colleague actually managed to resolve the issue completely!
To answer your questions and give a little bit more context about our architecture:
1. Deployment vs. Local Environment: Yes, the app is currently run locally on our machines (Docker Desktop + WSL2) for development, but the final deployment is on a dedicated VM server hosted at the City Hall. The redirection to localhost:8433 is expected and required for local development. However, in production, it must redirect to the official domain https://frotas.umuarama.pr.gov.br (default port 443, without showing any port).
2. SNI Routing: In production, the City Hall’s infrastructure already uses a reverse proxy at the edge to handle domains on port 443. They mapped our specific container to the internal port 8433 on our VM, and we do not have access to configure their edge proxy. Locally, using SNI (like a central Traefik/NGINX proxy) is viable, but we really wanted to keep the local setup as simple as possible for other developers (just running docker compose up without needing to configure local reverse proxies on their hosts).
How we finally solved it:
The root cause was indeed a conflict between how our container’s internal NGINX block was handling redirects and how the host/proxy was mapping the ports. We tackled it on two fronts:
-
Application Layer (Laravel): We managed to solve the Laravel asset/redirect issue cleanly by adding the following to AppServiceProvider.php:
if (config('app.env') === 'production') { URL::forceScheme('https'); URL::forceRootUrl(config('app.url')); }
This forces Laravel to generate URLs using the APP_URL variable defined in the .env file, bypassing whatever NGINX passes via SERVER_PORT. Locally .env has the port (https://localhost:8433), and in production, it omits it.
-
Infrastructure Layer (NGINX): To fix the redirect loops, our team refactored the NGINX configuration to avoid hardcoding any port numbers (like SERVER_PORT 443). Instead, they shifted to a more modular setup using NGINX include directives and dynamic variables, letting the container determine the request host and port dynamically from the incoming headers.
This combination solved both the asset loading issue and the redirect loop, making the same configuration work seamlessly across different developers’ local environments and production!
Also, your tip about using 302 redirects instead of 301 during local testing was an absolute lifesaver! It prevented many browser cache issues while we debugged this (which explains some ghost “Connection Refused” errors a colleague was getting).
Thank you again for the great insights! Cheers!
Hello Daniel,
Glad you solved this and many thanks for giving this context and explanations. Happy this 302 could help 
Cheers!