LilaPHP

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

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.

app/index.php
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

New

Combine 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

You can trigger a re-render of your React islands from standard JavaScript (e.g., from Twig templates, jQuery, or other non-React components) using the global function window.renderReactComponent(name).
This is useful for updating components that rely on shared client-side data like 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

This way you can use React to load directly from resources/js/pages, in case you want to use React directly without templates with Twig, passing the context directly. Lila creates the "HTML", with scripts, styles, meta tags, langs, body and the main React component.

 //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

New

Use 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
Example Usage

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

New

You 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.

LilaPHP Debug Dashboard

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

New

You 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

New

Automatically 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

Automatic

You 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

SQLite - Zero configuration
MySQL/MariaDB - Production ready
PostgreSQL - Advanced features
$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
What it does:
  • Creates database if it doesn't exist (MySQL/PostgreSQL only)
  • Scans app/ for models extending BaseModel
  • Creates tables from model FieldDatabase attributes
  • Records migrations in migrations table
migrate:run

Runs migrations only if the database already exists. Use this when the database is already created.

php app/cli.php migrate:run
Use case: When database exists but tables need to be created or updated.
migrate:status

Shows the current migration status for all models.

php app/cli.php migrate:status
Output example:
✓ 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
Use case: Development environment when you need to reset the database completely.

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.

Workaround: Use 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
Generated file: 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
Generated file: 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 length
decimals - Decimal places
nullable - Allow NULL
default - Default value
primaryKey - Primary key
autoIncrement - Auto-increment
unique - Unique constraint
index - Create index
unsigned - Unsigned (MySQL)
comment - Column comment
Field Attributes (Validation)
required - Field is required
format - Validation format (email, url)
min_length - Min string length
max_length - Max string length
min - Min numeric value
max - Max numeric value
messages - Custom validation messages

Example: 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

1
php app/cli.php migrate:create

Create database and run initial migrations

2
php app/cli.php seed:create UserSeeder

Create a seeder for test data

3
php app/cli.php seed:run

Populate database with test data

4
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=false in .env
  • Ensure app/ directory is denied in Nginx/Apache config
  • Run npm run build in app/ 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