LilaPHP Framework
A lightweight, modular, and performance-first PHP micro-framework designed for simplicity, flexibility, and security. Install in seconds with Composer, runs on any server (Nginx, Apache, LAMPP, XAMPP, WAMP, FrankenPHP, Swoole), and combines the power of React Islands + Twig for modern, hybrid applications.
High Performance
Minimal footprint, optimized assets, and zero unnecessary overhead per request.
Secure by Default
CSRF protection, sanitization, and secure headers built-in and auto-configured.
Modular Design
Independent configuration per route or endpoint. Use only what you need.
React
React render full page and components as islands
Vite
Vite ready and configured for dev and production
PHP
And PHP pure code pre-configured, modular, modifiable and ready to use
One-Command Installation
Install via Composer in seconds. No complex setup, no build tools required to start.
Universal Server Support
Works on Nginx, Apache, LAMPP, XAMPP, WAMP, FrankenPHP, Swoole, VPS, or shared hosting.
React Islands + Twig
Combine server-side Twig templates with interactive React components. Best of both worlds.
Multi-Database Support
Built-in support for SQLite, MySQL, and PostgreSQL with automatic migrations.
Production-Ready Optimizations
Auto-minification, WebP image optimization, Twig caching, and more when DEBUG=false.
Zero Boilerplate
Clean, minimal code. No unnecessary abstractions. Full control over your application.
Why Choose LilaPHP?
LilaPHP avoids the monolithic approach of traditional frameworks.
Instead of loading a massive global state, LilaPHP instantiates
only what is required for the specific request. No complex setup—just run
composer create-project and you're ready to go.
This means faster cold starts, lower memory usage, and the ability to run multiple "micro-apps" within the same codebase—each with different security policies, headers, or middleware configurations.
Universal compatibility: Works seamlessly on Nginx, Apache, LAMPP, XAMPP, WAMP, FrankenPHP, Swoole, VPS, shared hosting, or PHP's built-in server. Deploy anywhere without configuration headaches.
include_once "./app/index.php";
use Core\App;
// Standard App
$app = new App();
// Admin Panel (Strict Security)
$adminApp = new App([
'security' => ['cors' => false, 'sanitize' => true,'logger'=>false, 'csrf' => true,'rateLimit'=>30],
'translate'=>true
]);
// Public API (Open Access)
//rateLimit 300 request for 1 minute per IP address
$apiApp = new App([
'security' => ['cors' => true, 'csrf' => false,'rateLimit'=>300],
'translate'=>true
]);
Installation
Get started in under 60 seconds with Composer. No complex configuration, no build steps required.
1. Install with Composer
composer create-project seip25/lila-php LilaPHP
2. Edit URL_PROJECT in app/.env
3. Start the Server
Option A: Use PHP's built-in server (development):
php -S localhost:8000
Option B: Visit http://localhost/LilaPHP directly with
LAMPP, XAMPP, WAMP,
Nginx, or Apache. Works out of the box!
React Integration
NewCombine the best of both worlds: server-side rendering with Twig and interactive React components using the Islands Architecture pattern. Mount React islands anywhere in your Twig templates, or render full React pages. Enjoy Hot Module Replacement (HMR) during development and optimized builds for production.
Development
cd app
npm install
npm run dev
Starts HMR server at port 5173
Usage in Twig
<head>
{{ vite_assets() }}
</head>
<body>
{{ react('Header', {'user': {'name': 'Lila'}, 'cart': 3}) }}
<main class="p-4">
<h1>Standard PHP Content</h1>
{{ react('Counter', {'start': 10}) }}
</main>
</body>
Important: Always run npm commands from the
app/
directory, not the project root.
Re-rendering React Islands
window.renderReactComponent(name).
localStorage from other independent DOM elements without needing a complete page
reload.
// Re-render all React components on the page
renderReactComponent();
// Re-render only components named 'CartBadge'
renderReactComponent('CartBadge');
React pages templates
//index.php
//app/resources/js/pages/ReactIsland.jsx
return $app->renderReact("ReactIsland", [
"app" => [
"debug" => $app->getEnv("DEBUG")
]
], [
"lang" => "es",
"title" => "React pages + LilaPHP",
"meta" => [
["name" => "description", "content" => "React pages example meta description"]
],
"scripts" => [
"https://cdn.tailwindcss.com"
]
]);
Routing
Zero-config routing system with method injection and middleware support.
GET, POST, PUT, DELETE, PATCH, OPTIONS...
include_once "./app/index.php";
use Core\App;
$app=new App();
/*
Example with logger and cors
$app = new App([
'security' => [
'cors' => false,
'sanitize' => true,
'logger' => true,
'rateLimit' => 200
],
'translate' => false
]);
*/
$app->get(callback: function($req, $res) use ($app) {
return $app->render("home", ['title' => 'Welcome']);
});
$app->post(callback: function($req, $res) use ($app) {
return $app->jsonResponse(["success" => true]);
}, csrf: true,
middlewares: [fn($req, $res) => new LoginModel(data: $req, lang: $lang)]
);
Routing Attributes
NewUse PHP 8 Attributes to define HTTP methods, CSRF protection, and middlewares directly on your handlers.
Available Attributes
#[GET],#[POST]#[PUT],#[DELETE]
#[CSRF]- Enable CSRF validation
include_once "./app/index.php";
use Core\GET;
use Core\POST;
use Core\CSRF;
use Core\App;
$app=new App();
// Register using attributes for functions or methods
#[GET]
function login($req, $res)
{
global $app;
return $app->render("login");
}
$app->add(callback: 'login');
#[POST]
#[CSRF]
function loginPost($req, $res)
{
global $app;
return $app->jsonResponse(["success" => true]);
}
$app->add(callback: 'loginPost', middlewares: [fn($req, $res) => new LoginModel(data: $req, lang: $lang)]);
//With class
class Example{
#[GET]
static function GET($req,$res){
global $app;
return $app->render("login");
}
}
$app->add(callback: [Example::class,'GET'] );
Configuration
Settings in .env control the framework's behavior
globally.
TITLE_PROJECT="LILA PHP Framework"
DEBUG=true
VERSION_PROJECT="0.1"
VERSION_API="1"
PATH_LOGS="logs/"
PATH_LOCALES="locales/"
SECRET_KEY="mysecretkey"
URL_PROJECT="http://localhost/LilaPHP" #for cli http://localhost:8000
LANG="esp"
DB_NAME="lila"
DB_PROVIDER="sqlite"
#DB_PROVIDER="mysql"
#DB_USER="root"
#DB_PASSWORD=""
#DB_HOST="localhost"
#DB_PORT="3306"
App Core
The App class is the heart of LilaPHP. It initializes
configuration, handles routing, executes middleware, and manages
responses.
$app->redirect('/dashboard'); //redirect to a route
$app->jsonResponse(['ok' => true]); //return a json response
$app->render('home', ['user' => $user]);//render a view with twig and context
$app->translate('hello'); // return key 'hello' in current language
$app->translations(); // return all translations
$app->getEnv('DEBUG'); //return the value of the environment variable DEBUG
$app->generateCsrf(); //generate a csrf token
$app->validateCsrf(); //validate a csrf token
$app->run(); //run the app
Responses
LilaPHP provides straightforward methods to send HTML, JSON, and manage dynamic output.
$app->render($template, $data)
Renders a view with Twig and passes context automatically.
$app->render('home', ['title' => 'Welcome']);
$app->jsonResponse($data, $code)
Returns a JSON encoded response with appropriate headers and status code.
$app->jsonResponse(['success' => true], 200);
Response Caching
NewYou can cache your responses for a specified amount of time. Excellent for heavy queries or static-like APIs. Supports transparent Output Buffering.
Option A: Attribute Style
Add the Attribute directly above your route handler function or method.
use Core\Cache;
#[GET]
#[Cache(seconds: 60)] // Cache response for 1 minute
function index($req, $res) {
echo "Dynamic but Cached at " . time();
}
Option B: Middleware Style
Pass the execution middleware array when registering route manually.
$app->add(callback: 'handler', middlewares: [$app->cacheResponse(60)]);
Security
Integrated CSRF protection, input sanitization automatically, secure headers, cors, rate limiting.
CSRF Protection
Enable properly by adding csrf: true to your routes.
$app->post(..., csrf: true);
Then use the helper in forms:
{{ csrf_input() }}
And send "_csrf" key , and value csrf_input in fetch body
function exampleFetchWithCSRF(event) {
event.preventDefault();
const body = {
email: event.target.email.value,
password: event.target.password.value,
_csrf: event.target._csrf.value
};
const r = await fetch("{{url('/login/')}}", {
method: "POST",
body: JSON.stringify(body),
headers: {
"Content-Type": "application/json",
"X-CSRF-Token": body._csrf,
},
credentials: "include",
});
....
}
Debugging
Dev Tool
LilaPHP includes a built-in request profiler that tracks execution
time, memory usage, and request details. This feature is only active
when DEBUG=true in your .env file.
How to Access
Simply append ?debug to your application URL root to
view the dashboard.
http://localhost/LilaPHP/?debug
The dashboard allows you to view historical request metrics and clear the logs.
Middleware
LilaPHP includes a built-in middleware system that allows you to execute code before and after your routes are executed.Or you can add middleware to a specific route and method.
include_once "./app/index.php";
use Core\BaseModel;
use Core\Field;
use Core\App;
$app = new App();
class LoginModel extends BaseModel
{
#[Field(required: true, format: "email")]
public string $email;
#[Field(required: true, min_length: 6)]
public string $password;
}
$lang = $app->getSession(key: "lang", default: "es");
$app->post(
callback: function ($req, $res) use ($app, $lang) {
return $app->jsonResponse(["success" => true, "login" => false, "session" => $lang]);
},
middlewares: [
fn($req, $res) => new LoginModel(data: $req, lang: $lang),
//Middleware for "/login" post method with validation BaseModel
],
csrf: true
);
//Or use with attributes
use Core\Middleware;
function testingMiddleware($req, $res) {
error_log(message: "Testing middleware");
}
function testingMiddlewareRepeat($req, $res) {
error_log(message: "Testing middleware repeat");
}
#[GET]
#[Middleware('testingMiddleware')]
#[Middleware('testingMiddlewareRepeat')]
function login($req, $res) {
global $app;
$lang = $app->getSession(key: "lang", default: "es");
return $app->jsonResponse(["success" => true, "login" => false, "session" => $lang]);
}
$app->add(callback:"login");
//Global middlewares after and before for "/login" all methods in route
$app->addMiddlewares(middlewares: [
'before' => [
fn($req, $res) => error_log(message: "Custom before route")
],
'after' => [
fn($req, $res) => error_log(message: "Custom after route")
]
]);
$app->run();
Middleware Attributes
NewYou can declare middlewares directly using attributes on your route methods or functions.
use Core\Middleware;
class Auth {
static function verify($req, $res) {
if (!isset($_SESSION['user'])) header('Location: /login');
}
}
#[GET]
#[Middleware([Auth::class, 'verify'])]
function dashboard($req, $res) {
echo "Welcome to Dashboard";
}
Validation
Clean, declarative validation using PHP 8 attributes inside model classes.
Model Definition
use Core\BaseModel;
use Core\Field;
class LoginModel extends BaseModel {
#[Field(required: true, format: "email")]
public string $email;
#[Field(required: true, min_length: 6)]
public string $password;
}
Route Usage
$app->post(
callback: fn($req, $res) => $app->jsonResponse(["ok" => true]),
middlewares: [
fn($req) => new LoginModel(data: $req)
]
);
Attribute Validation
NewAutomatically validate incoming request data
against a model using the #[Validate] Attribute. It triggers existing exception
streams upon failure.
use Core\Validate;
use Core\Field;
use Core\CSRF;
class LoginModel extends BaseModel {
#[Field(required: true, format: "email")]
public string $email;
#[Field(required: true, min_length: 6)]
public string $password;
}
#[POST]
#[CSRF]
#[Validate(LoginModel::class)]
function loginPost($req, $res) {
return $app->jsonResponse(["success" => true]);
}
$app->add(callback: 'loginPost');
$app->run();
Language Override
You can specify a custom language string to bypass session language detection.
#[POST]
#[CSRF]
#[Validate(LoginModel::class, langParam: 'en')]
function loginPost($req, $res) { ... }
Sessions
LilaPHP supports normal and encrypted sessions. Encrypted sessions use AES-256-GCM internally for secure storage.
// Basic session usage
$app->setSession("user_id", 1);
$user = $app->getSession("user_id");
$app->removeSession("user_id");
// Encrypted session (recommended for authentication)
$app->setSession(
key: "auth",
value: $user,
encrypt: true
);
$user = $app->getSession(
key: "auth",
default: null,
decrypt: true
);
When encrypt: true is used, session data is encrypted before being stored.
Use decrypt: true when retrieving secure session values.
Basic Login Example (Secure Session)
include_once "./app/index.php";
use Core\App;
use Core\BaseModel;
use Core\Field;
use Core\CSRF;
use Core\POST;
use Core\Validate;
$app = new App();
class LoginModel extends BaseModel
{
#[Field(required: true, format: "email")]
public string $email;
#[Field(required: true, min_length: 6)]
public string $password;
}
$lang = $app->getSession(key: "lang", default: "es");
#[POST]
#[CSRF]
#[Validate(LoginModel::class)]
function loginPost($req, $res)
{
global $app;
//For testing debug result
if ($app->hasSession(key: "auth")) {
$user = $app->getSession(
key: "auth",
default: null,
decrypt: true
);
return $app->jsonResponse([
"success" => true,
"user" => $user
]);
}
$db = $app->getDatabaseConnection();
$query = "SELECT id, name, password
FROM users
WHERE email = ?
AND is_active = 1";
$email = trim($req["email"] ?? "");
$stmt = $db->prepare($query);
$stmt->execute([$email]);
$user = $stmt->fetchObject();
$password = trim($req["password"] ?? "");
if ($user) {
$passwordDB = $user->password;
unset($user->password);
if (password_verify($password, $passwordDB)) {
session_regenerate_id(true);
$app->setSession(
key: "auth",
value: $user,
encrypt: true
);
return $app->jsonResponse([
"success" => true
]);
}
}
// Always verify password even if user not found
// to prevent user enumeration timing attacks
$fakeHash = '$2y$10$usesomesillystringfore7hnbRJHxXVLeakoG8K30oukPsA.ztMG';
$fakeVerify = password_verify($password, $fakeHash) && $user;
return $app->jsonResponse(
data: ["success" => false],
code: 401
);
}
$app->add(callback: 'loginPost');
$app->run();
Includes CSRF protection
Model validation via attributes
Secure password verification
Session fixation protection
Encrypted authentication session
Timing attack protection
Translations
Multi-language support out of the box. Translation files live in
app/locales/.
eng.php
app/locales/return [
"welcome" => "Welcome!",
"login" => [
"title" => "Login"
]
];
Usage (Twig)
<h1>{{ translate("welcome") }}</h1>
<h2>{{ translate("login.title") }}</h2>
Global Language Switcher
AutomaticYou can switch the user session language
on-the-fly from any URL node simply by passing the set-lang=true parameter!
<!-- Example for GET o in HTML : ?set-lang=true&lang=en -->
<div class=" px-8 py-4 ...">
<a href="?set-lang=true&lang=eng" ...>English</a>
<a href="?set-lang=true&lang=esp" ...>Español</a>
<a href="?set-lang=true&lang=bra" ...>Português</a>
</div>
To prevent redirects and simply receive a status payload back
instead, append &redirect=false giving you a structured JSON response bundle.
Twig Functions
LilaPHP extends Twig with powerful helpers for assets, SEO, and security.
| Function | Description |
|---|---|
| image(file, w, h, q, type) | Generates optimized (WebP) user-scaled images on the fly. |
| csrf_input() | Outputs a hidden input with the CSRF token. |
| translate(key) | Returns translated string based on session lang. |
| vite_assets() | Injects HMR scripts (dev) or build assets (prod). |
| react(component, props) | Mounts a React component island. |
Database
Robust PDO wrapper with automatic retry, transaction support, and
multi-database connectivity. Switch between SQLite,
MySQL, and PostgreSQL with a simple .env change.
Supported Databases
$db = $app->getDatabaseConnection();
/**
Or pass connection parameters
$db = $app->getDatabaseConnection(
'mysql',
'localhost',
'root',
'',
'test',
3306
);
**/
$stmt = $db->prepare("SELECT * FROM users WHERE id = ?");
$stmt->execute([1]);
$user = $stmt->fetch();
Note:
When using $app->getDatabaseConnection() without parameters, it uses the database
configuration defined in app/.env. If no configuration is found, it defaults to
SQLite.
Use connection with PDO PHP native methods
CLI & Migrations
LilaPHP includes a powerful CLI tool for managing database migrations and seeders. Supports SQLite, MySQL, and PostgreSQL with automatic schema generation from model definitions.
Database Configuration
Configure your database provider in app/.env:
# SQLite (Default)
DB_PROVIDER="sqlite"
DB_NAME="lila"
# MySQL
DB_PROVIDER="mysql"
DB_NAME="lila"
DB_USER="root"
DB_PASSWORD=""
DB_HOST="localhost"
DB_PORT="3306"
# PostgreSQL
DB_PROVIDER="pgsql"
DB_NAME="lila"
DB_USER="postgres"
DB_PASSWORD=""
DB_HOST="localhost"
DB_PORT="5432"
Migration Commands
migrate:create
Recommended
Creates the database (if it doesn't exist) and runs all pending migrations. This is the recommended command for initial setup.
php app/cli.php migrate:create
- Creates database if it doesn't exist (MySQL/PostgreSQL only)
- Scans
app/for models extendingBaseModel - Creates tables from model
FieldDatabaseattributes - Records migrations in
migrationstable
migrate:run
Runs migrations only if the database already exists. Use this when the database is already created.
php app/cli.php migrate:run
migrate:status
Shows the current migration status for all models.
php app/cli.php migrate:status
✓ posts (from Models\Post)
✓ users (from Models\User)
✗ categories (from Models\Category) - NOT MIGRATED
migrate:fresh
Destructive
Drops all tables and re-runs migrations. ⚠️ This will delete all data!
php app/cli.php migrate:fresh
Warning: You will be prompted to confirm with "yes" before execution.
migrate:rollback
In
Development
Rollback functionality is currently in development and not yet implemented.
migrate:fresh to drop and recreate tables, or
manually drop specific tables.
Seeder Commands
seed:create
Creates a new seeder file in app/cli/seeders/.
php app/cli.php seed:create UserSeeder
app/cli/seeders/UserSeeder.php
namespace Cli\Seeders;
class UserSeeder
{
private PDO $db;
public function __construct(PDO $db)
{
$this->db = $db;
}
public function run(): void
{
$stmt = $this->db->prepare("
INSERT INTO users (email, password, created_at)
VALUES (?, ?, ?)
");
$currentTime = date('Y-m-d H:i:s');
$users = [
['user1@example.com', password_hash('password123', PASSWORD_DEFAULT), $currentTime],
['user2@example.com', password_hash('password123', PASSWORD_DEFAULT), $currentTime],
];
foreach ($users as $user) {
$stmt->execute($user);
}
}
}
seed:run
Executes all seeders in app/cli/seeders/ directory.
php app/cli.php seed:run
Model Generator
model:create
Creates a new model file in app/models/ with comprehensive documentation and
examples.
php app/cli.php model:create Product
app/models/Product.php
What's Included in Generated Models
- Standard fields:
id,created_at,updated_at - Example fields: All field types commented with usage examples
- Complete documentation: PHPDoc for every field
- Field types reference: List of all available types
- Attributes guide: All FieldDatabase and Field options explained
Field Attribute
Used for validation when processing requests via middleware
#[Field(
required: true,
format: 'email',
min_length: 3,
max_length: 100,
messages: ['email' => 'Invalid email']
)]
public string $email;
FieldDatabase Attribute
Used for database schema in automatic migrations
#[FieldDatabase(
type: 'varchar',
length: 255,
unique: true,
index: true,
comment: 'User email'
)]
public string $email;
Available Field Types & Attributes
Numeric Types
int
bigint
smallint
tinyint
decimal
float
double
String Types
varchar
char
text
mediumtext
longtext
Date/Time Types
date
datetime
timestamp
time
Other Types
boolean
json
FieldDatabase Attributes
type - Column type (required)length - Column lengthdecimals - Decimal placesnullable - Allow NULLdefault - Default valueprimaryKey - Primary keyautoIncrement - Auto-incrementunique - Unique constraintindex - Create indexunsigned - Unsigned (MySQL)comment - Column commentField Attributes (Validation)
required - Field is requiredformat - Validation format (email, url)min_length - Min string lengthmax_length - Max string lengthmin - Min numeric valuemax - Max numeric valuemessages - Custom validation messagesExample: Complete Model
<?php
namespace Models;
use Core\BaseModel;
use Core\Field;
use Core\FieldDatabase;
class Product extends BaseModel
{
#[FieldDatabase(type: 'int', primaryKey: true, autoIncrement: true)]
protected int $id;
#[FieldDatabase(type: 'datetime', default: 'CURRENT_TIMESTAMP')]
protected string $created_at;
#[Field(required: true, min_length: 3, max_length: 100)]
#[FieldDatabase(type: 'varchar', length: 100, comment: 'Product name')]
public string $name;
#[Field(required: false, min: 0)]
#[FieldDatabase(type: 'decimal', length: 10, decimals: 2, unsigned: true)]
public float $price;
#[FieldDatabase(type: 'boolean', default: '1')]
public bool $is_active = true;
}
Typical Workflow
php app/cli.php migrate:create
Create database and run initial migrations
php app/cli.php seed:create UserSeeder
Create a seeder for test data
php app/cli.php seed:run
Populate database with test data
php app/cli.php migrate:status
Verify all migrations completed successfully
Other Commands
help
Shows all available CLI commands
Production Deployment
LilaPHP is production-ready out of the box. Simply set DEBUG=false and
deploy to any server—Nginx, Apache, LAMPP, XAMPP, WAMP, VPS, or shared hosting.
Automatic Optimizations (when DEBUG=false)
- HTML Minification - Automatic compression of all HTML output
- Twig Template Caching - Pre-compiled templates in
app/cache/ - Asset Caching - Cached assets in
public/cache/
Security Checklist
- Set
DEBUG=falsein.env - Ensure
app/directory is denied in Nginx/Apache config - Run
npm run buildinapp/for React assets (if using React) - Enable HTTPS with SSL certificates
- Configure proper file permissions (755 for directories, 644 for files)
- Ensure required PHP extensions (GD, mbstring, etc.) are installed
Server Setup Guide (Ubuntu/Debian)
1. PHP 8.4 Installation
Add the official PHP repository and install PHP 8.4 with key modules:
# Add official PHP repository (Ondřej Surý)
sudo add-apt-repository ppa:ondrej/php -y
sudo apt update
# Install PHP 8.4 and key modules
sudo apt install -y php8.4-fpm php8.4-mysql php8.4-gd php8.4-opcache \
php8.4-curl php8.4-mbstring php8.4-xml php8.4-zip php8.4-bcmath
2. Composer Installation
Install the PHP package manager globally:
# 1. Download the installer
php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
# 2. Run the installation and move to system binaries
sudo php composer-setup.php --install-dir=/usr/local/bin --filename=composer
# 3. Remove the temporary installer
php -r "unlink('composer-setup.php');"
3. Opcache Configuration
Optimize production performance by enabling Opcache in your php.ini:
# Edit your php.ini (e.g., /etc/php/8.4/fpm/php.ini)
opcache.enable=1
opcache.memory_consumption=256
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=20000
opcache.validate_timestamps=0
4. PHP-FPM Pool Optimization
Optimize /etc/php/8.4/fpm/pool.d/www.conf process management:
pm = static
pm.max_children = 60
pm.max_requests = 1000
listen.backlog = 1024
5. Nginx Configuration
Full virtual host configuration with SSL and optimized PHP-FPM support:
server {
listen 80;
listen [::]:80;
server_name your-domain.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name your-domain.com;
# ssl_certificate /etc/letsencrypt/live/your-domain.com/fullchain.pem;
# ssl_certificate_key /etc/letsencrypt/live/your-domain.com/privkey.pem;
# include /etc/letsencrypt/options-ssl-nginx.conf;
# ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
# ssl_certificate /etc/nginx/ssl/your-domain.com/your-domain.com.crt;
# ssl_certificate_key /etc/nginx/ssl/your-domain.com/your-domain.com.key;
root /var/www/your-project;
index index.php index.html index.htm;
access_log /var/log/nginx/your-project_access.log;
error_log /var/log/nginx/your-project_error.log;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/var/run/php/php8.4-fpm.sock;
fastcgi_buffers 16 16k;
fastcgi_buffer_size 32k;
fastcgi_read_timeout 300s;
fastcgi_send_timeout 300s;
}
location ~ ^/(app|vendor|composer\.json|composer\.lock|package\.json|package-lock\.json|AGENTS\.md) {
deny all;
return 404;
}
location ~ /\.(ht|git|env) {
deny all;
}
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
expires max;
log_not_found off;
}
}
6. Folder Permissions
Ensure the web server has the correct ownership and permissions:
# Set web server as owner
sudo chown -R www-data:www-data /var/www/your-project
# Set correct permissions
sudo chmod -R 755 /var/www/your-project