The two nginx config line that cost me hours of troubleshooting


Our business website went some database upgrades. We jumped from using sqlite to mysql for container separation, file management and better oversight control. Not only this we implemented filamentphp as the admin panel. Right now it is a very simple page but improving in scalability.
Currently the only thing changeable over the admin panel is the project delivery page. It can create and edit projects, assign them to clients. This is supposed to create an api for project management systems. Send over data in remote locations or to any editor online. Literally have access to business data at one place yet accessible from anywhere.
The server runs docker compose with two containers one is the nginx with php-fpm…, just everything except mysql db which is the second container. To run it on localhost with docker compose, have the same image able to run anywhere, I mean even locally without a domain. It doesn’t support ssl straight away. The ssl is provided with a second layer of reverse-proxy nginx proxy manager (npm) which is the ‘front-line’ of the internet access. It has access to specific domains (and subdomains), is routed with A names over DNS directly. This is crucial to obtain ssl from let’s encrypt using dns challenge.
Main issue in this scenario happens in between them two nginx-es or let’s say the relationship of laravel and the npm. The website itself support full ssl but the laravel doesn’t know it.
Force HTTPS
Anytime the server is exposed to internet most probably the environment wouldn’t be local. This has to be set in the .env / environment variable. Once the server is in production, test, beta… anything it forces the url scheme to be https using the code below. It has to be put into the boot method in app service provider.
if (! app()->isLocal()) {
URL::forceScheme('https');
}
This step seems to work. Well it does for many URL generations in the web such as the forms, assets. But I ran into 401 unauthorized errors, when uploading file in filament. Just an image thumbnail. This was happening due to livewire url validation. Somehow the full url in the request was still happening to be http.
I ran a debug code in a route of debug to see this value.
Route::get('/debug-signed', function (Request $r) {
$expires = (int) $r->query('expires');
return [
'full_url' => $r->fullUrl(),
'has_valid_signature' => $r->hasValidSignature(),
'expires_parameter' => $expires,
'now_unix' => now()->timestamp,
'app_url' => config('app.url'),
];
})->name('debug-signed');
The response was:
{"scheme":"http","is_secure":false,"x-forwarded-proto":"https","x-forwarded-port":null,"all_forwarded":{"proto":"https","for":"9...160"}}
Trust Proxy
The Laravel app has to know about the fact that it is sitting behind these nginx-es and the fact that they can be trusted. Trusted in means they use ssl.
Let’s just put this line into the Http/Middleware/TrustProxies.php
file.
protected $proxies = '*';
Http Headers
To actually recieve the real https headers they have to be loaded in the request and passed in by the internal nginx. Using the lines below.
Http/Middleware/TrustProxies.php
protected $headers =
Request::HEADER_X_FORWARDED_FOR
| Request::HEADER_X_FORWARDED_HOST
| Request::HEADER_X_FORWARDED_PROTO;
fastcgi_param HTTPS $http_x_forwarded_proto;
fastcgi_param HTTP_X_FORWARDED_PROTO $http_x_forwarded_proto;
Redirect
At the end one more thing to add to the nginx proxy manager is to redirect every http url to https. This can be written in the edit → advanced → custom nginx configuration tab.
if ($scheme = 'http') {
return 301 https://$host$request_uri;
}
Conclusion
Something that seemed to be a livewire error refusing upload on the post request was just missed line in the nginx config not passing the headers.
Subscribe to my newsletter
Read articles from Marek Čulák directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
