What error are you getting in Log? Also did you apply
php artisan optimize:clear
Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.
Hello everyone, I have some issue to reach my Laravel API hosted on Render. Everything works fine in localhost (http: //localhost:8000/api/) but when I try with the Render url (https: //myapi.onrender.com/api), I get an error 404. I used this tutorial to deploy my api : https:// docs.render.com/deploy-php-laravel-docker and I'm using postman to make the requests. Does someone know how to fix this ? Thanks
What error are you getting in Log? Also did you apply
php artisan optimize:clear
Nothing in the log except this " ::1 - - [26/Sep/2024:21:07:46 +0000] "GET /api HTTP/1.1" 404 485 "-" "PostmanRuntime/7.42.0" "
Yes I did, in the "00-laravel-deploy.sh" file
Hello, I just encountered the same problem. Were you able to find the solution? Need help!
@Yahrod Hello, I just encountered the same problem. Were you able to find the solution? Need help!
So I did some digging. Apparently the issue is when I'm adding something behind the Render url... This "https: //myapi.onrender.com/" works but this "https: //myapi.onrender.com/api" does not.
So I told my self "maybe the routes are not created" so I added this commad "php artisan route:list" and here is the output :
POST api/products ...... products.store › ProductController@store
GET|HEAD api/products ...... products.index › ProductController@index
GET|HEAD api ............................ generated::lnwA0mh8RHztYh4y
As you can see, the routes are created. So I think the the issue is comming from the "nginx-site.conf" file :
server {
# Render provisions and terminates SSL
listen 80;
# Make site accessible from http://localhost/
server_name _;
root /var/www/html/public;
index index.html index.htm index.php;
# Disable sendfile as per https://docs.vagrantup.com/v2/synced-folders/virtualbox.html
sendfile off;
# Add stdout logging
error_log /dev/stdout info;
access_log /dev/stdout;
# block access to sensitive information about git
location /.git {
deny all;
return 403;
}
add_header X-Frame-Options "SAMEORIGIN";
add_header X-XSS-Protection "1; mode=block";
add_header X-Content-Type-Options "nosniff";
charset utf-8;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location = /favicon.ico { access_log off; log_not_found off; }
location = /robots.txt { access_log off; log_not_found off; }
error_page 404 /index.php;
location ~* \.(jpg|jpeg|gif|png|css|js|ico|webp|tiff|ttf|svg)$ {
expires 5d;
}
location ~ \.php$ {
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass unix:/var/run/php-fpm.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
include fastcgi_params+
}
# deny access to . files
location ~ /\. {
log_not_found off;
deny all;+
}
location ~ /\.(?!well-known).* {
deny all;
}
}
But I don't know where... Does someone sees what is wrong ?
It sounds like you're running into issues with routing when accessing your Laravel API on Render. Since the root URL works, but /api results in a 404 error, there are a few things to check and adjust:
Your Nginx configuration looks mostly correct, but ensure that the handling of routes and requests is correctly set up for Laravel. Here's a refined version of your nginx-site.conf configuration to help with Laravel routing:
server {
# Render provisions and terminates SSL
listen 80;
server_name myapi.onrender.com;
root /var/www/html/public;
index index.php index.html index.htm;
# Logging
error_log /dev/stdout info;
access_log /dev/stdout;
# Deny access to sensitive information
location /.git {
deny all;
return 403;
}
# Security headers
add_header X-Frame-Options "SAMEORIGIN";
add_header X-XSS-Protection "1; mode=block";
add_header X-Content-Type-Options "nosniff";
charset utf-8;
# Main location block for Laravel
location / {
try_files $uri $uri/ /index.php?$query_string;
}
# Additional location blocks
location ~ \.php$ {
include fastcgi_params;
fastcgi_pass unix:/var/run/php-fpm.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
}
location = /favicon.ico { access_log off; log_not_found off; }
location = /robots.txt { access_log off; log_not_found off; }
error_page 404 /index.php;
location ~* \.(jpg|jpeg|gif|png|css|js|ico|webp|tiff|ttf|svg)$ {
expires 5d;
}
# Deny access to dot files
location ~ /\. {
log_not_found off;
deny all;
}
location ~ /\.(?!well-known).* {
deny all;
}
}
Environment Configuration
Make sure your environment variables are correctly set on Render. Specifically, check your APP_URL in your .env file to ensure it matches your Render URL:
APP_URL=https://myapi.onrender.com
Testing Routes
Test your API routes using Postman with the complete URL (like https://myapi.onrender.com/api/products). Make sure that the routes listed in php artisan route:list are correctly prefixed with /api.
@jsanwo64 Hello and thank you for the answer !
I try to fix my file with yours suggestions but still the same issue... About the environment variables, here is what i have in Render :
Is something missing ? Should I use only my .env file ?
@Yahrod try that. Also, make sure the following database credentials match what's set in Render's environment:
DB_CONNECTION=pgsql
DB_HOST=<Render PG Host>
DB_PORT=5432
DB_DATABASE=<Database Name>
DB_USERNAME=<Database Username>
DB_PASSWORD=<Database Password>
APP_URL: As mentioned earlier, ensure that APP_URL is set correctly both in the .env file and in Render's environment variables:
APP_URL=https://myapi.onrender.com
CORS Configuration: Another potential issue could be CORS (Cross-Origin Resource Sharing). If you're using APIs from different clients, ensure your app allows requests from outside origins. You can configure this in config/cors.php. For testing, you can set the allowed_origins as follows:
'paths' => ['api/*'],
'allowed_origins' => ['*'],
then clear config cache
php artisan config:cache
@jsanwo64 So I tried to add the cors file but it gives me a new error :
In LoadConfiguration.php line 99:
array_merge(): Argument #2 must be of type array, int given
The database seems correct because the migration works
@Yahrod Check config/cors.php File: Make sure that the CORS configuration file does not contain any unexpected values or types. Here is a standard format for the config/cors.php file:
return [
'paths' => ['api/*', 'sanctum/csrf-cookie'],
'allowed_methods' => ['*'],
'allowed_origins' => ['*'],
'allowed_origins_patterns' => [],
'allowed_headers' => ['*'],
'exposed_headers' => [],
'max_age' => 0,
'supports_credentials' => false,
];
can you post what you have in your nginx-site.conf, RouteServiceProvider.php, .env and config/cors.php
nginx-site.conf :
server {
# Render provisions and terminates SSL
listen 80;
server_name myapi.onrender.com;
root /var/www/html/public;
index index.php index.html index.htm;
# Logging
error_log /dev/stdout info;
access_log /dev/stdout;
# Deny access to sensitive information
location /.git {
deny all;
return 403;
}
# Security headers
add_header X-Frame-Options "SAMEORIGIN";
add_header X-XSS-Protection "1; mode=block";
add_header X-Content-Type-Options "nosniff";
charset utf-8;
# Main location block for Laravel
location / {
try_files $uri $uri/ /index.php?$query_string;
}
# Additional location blocks
location ~ \.php$ {
include fastcgi_params;
fastcgi_pass unix:/var/run/php-fpm.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
}
location = /favicon.ico { access_log off; log_not_found off; }
location = /robots.txt { access_log off; log_not_found off; }
error_page 404 /index.php;
location ~* \.(jpg|jpeg|gif|png|css|js|ico|webp|tiff|ttf|svg)$ {
expires 5d;
}
# Deny access to dot files
location ~ /\. {
log_not_found off;
deny all;
}
location ~ /\.(?!well-known).* {
deny all;
}
}
.env :
APP_NAME=Laravel
APP_ENV=production
APP_KEY=same APP_KEY in render
APP_DEBUG=false
APP_TIMEZONE=UTC
APP_URL=https://myapi.onrender.com
APP_LOCALE=en
APP_FALLBACK_LOCALE=en
APP_FAKER_LOCALE=en_US
APP_MAINTENANCE_DRIVER=file
# APP_MAINTENANCE_STORE=database
BCRYPT_ROUNDS=12
LOG_CHANNEL=stack
LOG_STACK=single
LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=debug
DB_CONNECTION=pgsql
# DB_HOST= internal url from render
DB_PORT=5432
DB_DATABASE=db
DB_USERNAME=user
DB_PASSWORD=pwd
SESSION_DRIVER=database
SESSION_LIFETIME=120
SESSION_ENCRYPT=false
SESSION_PATH=/
SESSION_DOMAIN=null
BROADCAST_CONNECTION=log
FILESYSTEM_DISK=local
QUEUE_CONNECTION=database
CACHE_STORE=database
CACHE_PREFIX=
MEMCACHED_HOST=127.0.0.1
REDIS_CLIENT=phpredis
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
MAIL_MAILER=log
MAIL_HOST=127.0.0.1
MAIL_PORT=2525
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS="[email protected]"
MAIL_FROM_NAME="${APP_NAME}"
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=
AWS_USE_PATH_STYLE_ENDPOINT=false
VITE_APP_NAME="${APP_NAME}"
config/cors.php :
'paths' => ['api/*'],
'allowed_origins' => ['*'],
RouteServiceProvider.php :
<?php
namespace Illuminate\Foundation\Support\Providers;
use Closure;
use Illuminate\Contracts\Routing\UrlGenerator;
use Illuminate\Routing\Router;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Traits\ForwardsCalls;
/**
* @mixin \Illuminate\Routing\Router
*/
class RouteServiceProvider extends ServiceProvider
{
use ForwardsCalls;
/**
* The controller namespace for the application.
*
* @var string|null
*/
protected $namespace;
/**
* The callback that should be used to load the application's routes.
*
* @var \Closure|null
*/
protected $loadRoutesUsing;
/**
* The global callback that should be used to load the application's routes.
*
* @var \Closure|null
*/
protected static $alwaysLoadRoutesUsing;
/**
* The callback that should be used to load the application's cached routes.
*
* @var \Closure|null
*/
protected static $alwaysLoadCachedRoutesUsing;
/**
* Register any application services.
*
* @return void
*/
public function register()
{
$this->booted(function () {
$this->setRootControllerNamespace();
if ($this->routesAreCached()) {
$this->loadCachedRoutes();
} else {
$this->loadRoutes();
$this->app->booted(function () {
$this->app['router']->getRoutes()->refreshNameLookups();
$this->app['router']->getRoutes()->refreshActionLookups();
});
}
});
}
/**
* Bootstrap any application services.
*
* @return void
*/
public function boot()
{
//
}
/**
* Register the callback that will be used to load the application's routes.
*
* @param \Closure $routesCallback
* @return $this
*/
protected function routes(Closure $routesCallback)
{
$this->loadRoutesUsing = $routesCallback;
return $this;
}
/**
* Register the callback that will be used to load the application's routes.
*
* @param \Closure|null $routesCallback
* @return void
*/
public static function loadRoutesUsing(?Closure $routesCallback)
{
self::$alwaysLoadRoutesUsing = $routesCallback;
}
/**
* Register the callback that will be used to load the application's cached routes.
*
* @param \Closure|null $routesCallback
* @return void
*/
public static function loadCachedRoutesUsing(?Closure $routesCallback)
{
self::$alwaysLoadCachedRoutesUsing = $routesCallback;
}
/**
* Set the root controller namespace for the application.
*
* @return void
*/
protected function setRootControllerNamespace()
{
if (! is_null($this->namespace)) {
$this->app[UrlGenerator::class]->setRootControllerNamespace($this->namespace);
}
}
/**
* Determine if the application routes are cached.
*
* @return bool
*/
protected function routesAreCached()
{
return $this->app->routesAreCached();
}
/**
* Load the cached routes for the application.
*
* @return void
*/
protected function loadCachedRoutes()
{
if (! is_null(self::$alwaysLoadCachedRoutesUsing)) {
$this->app->call(self::$alwaysLoadCachedRoutesUsing);
return;
}
$this->app->booted(function () {
require $this->app->getCachedRoutesPath();
});
}
/**
* Load the application routes.
*
* @return void
*/
protected function loadRoutes()
{
if (! is_null(self::$alwaysLoadRoutesUsing)) {
$this->app->call(self::$alwaysLoadRoutesUsing);
}
if (! is_null($this->loadRoutesUsing)) {
$this->app->call($this->loadRoutesUsing);
} elseif (method_exists($this, 'map')) {
$this->app->call([$this, 'map']);
}
}
/**
* Pass dynamic methods onto the router instance.
*
* @param string $method
* @param array $parameters
* @return mixed
*/
public function __call($method, $parameters)
{
return $this->forwardCallTo(
$this->app->make(Router::class), $method, $parameters
);
}
}
lets try something.
# Handle API requests
location /api {
try_files $uri $uri/ /index.php?$query_string;
}
So it will look like this
server {
listen 80;
server_name myapi.onrender.com;
root /var/www/html/public;
index index.php index.html index.htm;
# Logging
error_log /dev/stdout info;
access_log /dev/stdout;
# Deny access to sensitive information
location /.git {
deny all;
return 403;
}
# Security headers
add_header X-Frame-Options "SAMEORIGIN";
add_header X-XSS-Protection "1; mode=block";
add_header X-Content-Type-Options "nosniff";
charset utf-8;
# Main location block for Laravel
location / {
try_files $uri $uri/ /index.php?$query_string;
}
# Handle API requests
location /api {
try_files $uri $uri/ /index.php?$query_string;
}
# PHP handling
location ~ \.php$ {
include fastcgi_params;
fastcgi_pass unix:/var/run/php-fpm.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
}
location = /favicon.ico { access_log off; log_not_found off; }
location = /robots.txt { access_log off; log_not_found off; }
error_page 404 /index.php;
location ~* \.(jpg|jpeg|gif|png|css|js|ico|webp|tiff|ttf|svg)$ {
expires 5d;
}
# Deny access to dot files
location ~ /\. {
log_not_found off;
deny all;
}
location ~ /\.(?!well-known).* {
deny all;
}
}
Route::get('/products', [ProductController::class, 'index'])->name('products.index');
Since you’re not seeing any helpful output in the logs, you may want to add some debugging or logging in your Laravel application. This can help to identify if the request is reaching the Laravel application at all.
In your routes/api.php, you can add temporary logging to check if the route is hit:
Route::get('/products', function () {
\Log::info('Products route hit');
return response()->json(['message' => 'Hello from products!']);
});
Please or to participate in this conversation.