Deployment Guide: RILT Stack, Nginx, PostgreSQL on Ubuntu
A practical production ready Laravel deployment guide using InertiaJS, React, TailwindCSS, PostgreSQL, Nginx on Ubuntu.
Modern Laravel applications frequently combine a powerful backend framework with a reactive frontend interface. A common and practical stack today is Laravel + InertiaJS + React with TailwindCSS, backed by PostgreSQL and deployed using Nginx on an Ubuntu server.
This guide documents a practical production deployment workflow for this stack. The goal is to keep the setup simple, maintainable, and secure, which is especially important for small development teams or internal business systems.
The guide assumes:
- Ubuntu 22.04 LTS or newer
- A Laravel project using InertiaJS + React
- Vite as the frontend build tool
- Access to a domain name
- A fresh server with sudo privileges
Architecture Overviewh2
A typical deployment architecture for this stack looks like the following:
User Browser │ ▼ NGINX │ ▼ PHP-FPM (Laravel) │ ▼ PostgreSQLFrontend assets are compiled during deployment:
React + Tailwind │ ▼ Vite Build │ ▼ Optimized Static Assets │ ▼ Served by NginxThis is a standard LEMP architectureh2
- Nginx efficiently serves static assets and proxies PHP requests.
- PHP-FPM executes Laravel application logic.
- PostgreSQL handles reliable relational data storage.
- Vite compiles and optimizes frontend assets.
This architecture is stable, well-supported, and easy to maintain.
Server Requirementsh2
Before deploying, the server should have the following components:
- Ubuntu 22.04+
- Nginx
- PHP 8.4 (FPM)
- PostgreSQL
- Node.js
- Composer
- Git
- Supervisor
- Certbot
1. Initial Server Setuph2
First update the system to ensure all packages are current.
sudo apt updateThis command refreshes the package list from Ubuntu repositories.
Next upgrade installed packages:
sudo apt upgrade -yThe -y flag automatically confirms installation prompts.
Tiph3
Running updates regularly helps avoid security vulnerabilities and dependency conflicts.
2. Install Basic Utilitiesh2
Install common tools required for deployment.
sudo apt install nginx git unzip curl software-properties-common -yExplanation of each package:
| Package | Purpose |
|---|---|
| nginx | Web server |
| git | Pull project code from repository |
| unzip | Extract archives |
| curl | Download remote files |
| software-properties-common | Manage repositories |
Verify nginx installation:
nginx -v3. Create a Deployment User (Recommended)h2
Running deployments as root is not recommended.
Create a new user:
sudo adduser deployThis creates a dedicated deployment user.
Grant sudo privileges:
sudo usermod -aG sudo deploy-aGappends the user to a groupsudoallows administrative privileges
Switch to the new user:
su - deploy4. Install PHP 8.4h2
Ubuntu repositories may not always include the latest PHP version. We use the Ondřej Surý repository, which maintains modern PHP builds.
Add the repository:
sudo add-apt-repository ppa:ondrej/phpUpdate package lists:
sudo apt updateInstall PHP 8.4 and Laravel extensions:
sudo apt install php8.4 php8.4-fpm php8.4-pgsql php8.4-mbstring php8.4-xml php8.4-bcmath php8.4-curl php8.4-zip php8.4-gd php8.4-cli -yExplanation of Important Extensionsh3
| Extension | Purpose |
|---|---|
| php-fpm | Executes PHP scripts |
| pgsql | PostgreSQL database driver |
| mbstring | Multibyte string handling |
| xml | XML parsing |
| bcmath | Arbitrary precision math |
| curl | HTTP requests |
| zip | Archive management |
Verify installation:
php -vPHP-FPM runs PHP as a service and handles concurrent requests.
5. Install Composerh2
Composer manages PHP dependencies for Laravel.
Download Composer installer:
curl -sS https://getcomposer.org/installer | phpMove it globally:
sudo mv composer.phar /usr/local/bin/composerVerify installation:
composer --versionTip:
Using Composer globally allows running:
composer installfrom any project directory.
6. Install Node.jsh2
Laravel uses Node.js to build frontend assets.
Install Node.js 20:
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -Install Node:
sudo apt install nodejs -yVerify installation:
node -vnpm -v7. Install PostgreSQLh2
Install PostgreSQL server:
sudo apt install postgresql postgresql-contrib -ySwitch to postgres user:
sudo -u postgres psqlCreate database:
CREATE DATABASE <app_database>;change <app_database> to your database name
Create user:
CREATE USER <app_user> WITH PASSWORD <'strongpassword'>;change <app_user> to your database user name and <‘strongpassword’> to your database password
Grant privileges:
GRANT ALL PRIVILEGES ON DATABASE <app_database> TO <app_user>;Exit PostgreSQL:
\qTip:
Use strong passwords and avoid using the default postgres user for applications.
8. Deploy the Laravel Applicationh2
Move to web directory:
cd /var/wwwClone repository:
sudo git clone https://github.com/your-repository/project.git <app>Enter project folder:
cd <app>change <app> to your application name
Install PHP dependencies:
composer install --no-dev --optimize-autoloaderExplanation:
| Flag | Purpose |
|---|---|
| —no-dev | Skip development dependencies |
| —optimize-autoloader | Improve autoload performance |
Create environment file:
cp .env.example .envGenerate application key:
php artisan key:generateConfigure database:
DB_CONNECTION=pgsqlDB_HOST=127.0.0.1DB_DATABASE=<app_database>DB_USERNAME=<app_user>DB_PASSWORD=<strongpassword>Run database migrations:
php artisan migrate --force--force allows migrations to run in production.
9. Install Frontend Dependenciesh2
Install npm packages:
npm installBuild production assets:
npm run buildThis step compiles:
- React components
- TailwindCSS styles
- JavaScript assets
Vite outputs optimized files to:
public/buildTip:
Always run npm run build on production servers.
Never use npm run dev in production.
10. Set File Permissionsh2
Laravel requires write access to specific directories.
Avoid assigning full ownership to www-data.
Set ownership:
sudo chown -R root:root /var/www/<app>sudo chown -R www-data:www-data storage bootstrap/cacheSet permissions:
sudo chmod -R 775 storage bootstrap/cacheExplanation:
| Directory | Purpose |
|---|---|
| storage | logs, cache, sessions |
| bootstrap/cache | framework cache |
11. Configure Nginxh2
Create site configuration:
sudo nano /etc/nginx/sites-available/<app>This is vhost configuration so you can create multiple systems on the same server. Just repeat this step for each system.
Example configuration:
server { listen 80; server_name example.com www.example.com;
root /var/www/<app>/public; index index.php index.html;
charset utf-8;
location / { try_files $uri $uri/ /index.php?$query_string; }
location ~ \.php$ { include snippets/fastcgi-php.conf; fastcgi_pass unix:/run/php/php8.4-fpm.sock; }
# basic security restrictions location ~ /\. { deny all; } location ~* \.(env|log|sql)$ { deny all; }}Note: This is a basic configuration. You may need to adjust it based on your specific needs.
Enable vhost:
sudo ln -s /etc/nginx/sites-available/<app> /etc/nginx/sites-enabled/Test configuration:
sudo nginx -tRestart nginx:
sudo systemctl restart nginx12. Configure Firewall (UFW)h2
Ubuntu comes with UFW (Uncomplicated Firewall), which is enough for most setups.
The idea is simple: only allow what is needed, block everything else.
Enable Required Portsh2
Allow SSH first (important, or you can lock yourself out):
sudo ufw allow OpenSSHAllow HTTP and HTTPS:
sudo ufw allow 'Nginx Full'Explanation:
OpenSSH→ allows remote server access (port 22)Nginx Full→ allows port 80 (HTTP) and 443 (HTTPS)
Enable Firewallh2
sudo ufw enableThis activates the firewall.
Check status:
sudo ufw statusExpected output should show:
- OpenSSH → ALLOW
- Nginx Full → ALLOW
Optional: Restrict SSH (Recommended for Production)h2
Instead of allowing SSH from anywhere:
sudo ufw allow from YOUR_IP_ADDRESS to any port 22Then remove the default open rule:
sudo ufw delete allow OpenSSHThis limits SSH access to a specific IP.
Notesh2
- Do not enable UFW without allowing SSH first
- Always double-check rules before enabling
- Firewall helps reduce attack surface, especially for public servers
For internal/on-prem servers, this is still useful to prevent unnecessary access between networks.
12. Point Domain to Serverh2
Add an A Record in your DNS provider.
Example:
| Type | Name | Value |
|---|---|---|
| A | @ | YOUR_PUBLIC_IP |
Optional:
| Type | Name | Value |
|---|---|---|
| A | www | YOUR_PUBLIC_IP |
DNS propagation usually takes 5 minutes to several hours.
13. Install HTTPS (Let’s Encrypt)h2
Install certbot:
sudo apt install certbot python3-certbot-nginx -yGenerate SSL certificate:
sudo certbot --nginx -d example.com -d www.example.comCertbot automatically:
- requests certificates
- updates nginx configuration
- enables HTTPS redirect
Test renewal:
sudo certbot renew --dry-runCertificates renew automatically every 90 days.
14. Configure Laravel Schedulerh2
Open crontab:
crontab -eAdd scheduler entry:
* * * * * cd /var/www/<app> && php artisan schedule:run >> /dev/null 2>&1This executes scheduled tasks every minute.
Laravel internally determines which tasks should run.
15. Configure Queue Workers with Supervisorh2
Install Supervisor:
sudo apt install supervisor -yCreate worker config:
sudo nano /etc/supervisor/conf.d/<app>-worker.confExample configuration:
[program:<app>-worker]command=php /var/www/<app>/artisan queue:work --sleep=3 --tries=3 --timeout=90process_name=%(program_name)s_%(process_num)02dnumprocs=2autostart=trueautorestart=trueuser=www-dataredirect_stderr=truestdout_logfile=/var/www/<app>/storage/logs/worker.logSave and exit.
Reload supervisor:
sudo supervisorctl rereadsudo supervisorctl updatesudo supervisorctl start <app>-worker:*Make sure you replace <app> with your application name.
Check status:
sudo supervisorctl statusBasic Security Notesh2
- restrict write access to
storageandbootstrap/cacheonly - deny access to
.env, logs, and hidden files - validate file uploads in application level
Optional (php.ini):
disable_functions = exec,passthru,shell_exec,systemDeployment Checklisth2
Before marking deployment complete, verify:
- Environment variables configured
- Database connected
- Migrations executed
- Assets built
- Permissions set
- Nginx working
- HTTPS enabled
- Cron scheduler running
- Queue workers active
Final Thoughtsh2
Deploying a Laravel + InertiaJS + React application on Ubuntu does not require complex infrastructure. With Nginx, PHP-FPM, PostgreSQL, and a few supporting services, it is possible to run reliable production systems using a setup that is straightforward and proven.
This setup is not complex, but it is reliable.
For internal systems and small teams, this approach works well because it is:
- predictable
- easy to debug
- easy to maintain
Most environments, especially on-premise or VM-based, do not need overly engineered solutions. A clean LEMP setup with proper configuration is already enough to handle real workloads when the application is built correctly.
The goal is not to build the most advanced infrastructure, but to have a system that runs consistently, is easy to understand, and can be maintained without unnecessary complexity.