Innovate | Connect | Transform
ContactService statusUSD $

GICT knowledge base

How to Run Secure Laravel Apps on Ubuntu Using NGINX and PHP 8.3

Published

PHPnginxLaravel

Setting up a Laravel application in production has been one of my biggest challenges as a developer. I remember my first deployment – it took me three days and countless Google searches just to get a basic Laravel app running. The server kept crashing, SSL certificates wouldn’t work, and don’t even get me started on file permissions.

That was five years ago, and since then I’ve deployed dozens of Laravel applications using different approaches.

After all those deployments, successes, and failures, I’ve found that Laravel with NGINX and PHP 8.3 on Ubuntu creates the most reliable production environment. This combination has never let me down, even during heavy traffic periods or when dealing with complex applications.

Let me walk you through exactly how I set up this stack now. This isn’t theory – every step here comes from real production experience.

Why I Choose This Specific Stack

The Evolution of My Web Server Choice

I’ve tried almost every web server and PHP combination you can think of:

  • Apache with mod_php was my go-to for years until I hit performance walls that I couldn’t break through
  • LiteSpeed looked promising but the licensing costs were crazy for small projects
  • Even tried some exotic setups with Caddy and other newer servers

Why Laravel Won Me Over

Laravel became my framework of choice after struggling with custom PHP frameworks for too long. Writing authentication systems from scratch gets old fast. Laravel’s Eloquent ORM changed how I think about database interactions. Instead of writing raw SQL queries and worrying about injection attacks, I can write clean, readable code that’s automatically protected.

The Blade templating engine makes frontend work actually enjoyable. Before Laravel, I was mixing PHP and HTML in ways that made maintenance a nightmare. Blade keeps everything organized and includes helpful features like CSRF protection that just work without extra configuration.

NGINX: The Performance Game Changer

NGINX won me over during a project where we were expecting high traffic. Our Apache setup was struggling with just a few hundred concurrent users. We switched to NGINX on the same hardware and suddenly we could handle thousands of connections without any issues.

Pro Tip: What really impressed me about NGINX was how efficiently it serves static files. Images, CSS files, JavaScript bundles – NGINX handles these incredibly fast. This makes a huge difference when your application has lots of frontend assets.

PHP 8.3: Measurable Performance Gains

PHP 8.3 brings performance improvements that you can actually measure. I upgraded one of our applications from PHP 7.4 to 8.3 and saw immediate improvements in response times. The new features like named arguments and constructor property promotion also make code much more readable.

Ubuntu: Rock-Solid Stability

Ubuntu provides the stability I need for production servers. I’ve had Ubuntu servers running for over two years without any major issues. Security updates come regularly, the package management is reliable, and there’s always community support when something goes wrong.

Step 1: Preparing Your Ubuntu Server

Starting Fresh

I always start with a fresh Ubuntu installation, preferably 20.04 LTS or 22.04 LTS. The LTS versions give you five years of support, which matters for production applications that need to run for years without major changes.

System Updates First

The first step is always updating everything:

sudo apt update && sudo apt upgrade -y

Important: This might seem obvious, but I’ve learned not to skip it. Outdated packages cause weird issues that can take hours to debug. I once spent an entire afternoon troubleshooting SSL problems that were caused by an old version of OpenSSL.

Automatic Security Updates

Automatic security updates have saved me multiple times:

sudo apt install unattended-upgrades
sudo dpkg-reconfigure -plow unattended-upgrades

I’ve logged into servers to find critical security patches installed automatically overnight. This feature has prevented several potential security issues without any intervention from me.

Additional Security: For production servers, I also disable password authentication and use SSH keys only. Brute force attacks against SSH are constant on internet-facing servers. SSH keys eliminate this attack vector completely.

Step 2: Installing PHP 8.3 Properly

Adding the Required Repository

Getting PHP 8.3 on Ubuntu requires adding an external repository since it’s not in the default packages. I use Ondřej Surý’s PPA, which has been reliable for years. This maintainer is well-known in the PHP community and keeps packages updated quickly.

sudo add-apt-repository ppa:ondrej/php -y
sudo apt update

Installing PHP with All Required Extensions

sudo apt install php8.3 php8.3-fpm php8.3-cli php8.3-mysql php8.3-xml php8.3-mbstring php8.3-curl php8.3-zip php8.3-gd php8.3-bcmath php8.3-intl php8.3-soap php8.3-imap php8.3-redis php-opcache unzip -y

Note: I install more extensions than I immediately need because adding them later in production can be risky. The Redis extension is particularly important if you plan to use caching, which I recommend for any application handling real traffic.

Verification

Verify the installation worked:

php -v
php -m | grep -E "(curl|mbstring|xml|zip)"

PHP Configuration Tuning

PHP configuration tuning makes a significant difference in production. Edit the PHP-FPM configuration file:

sudo nano /etc/php/8.3/fpm/php.ini

These are the settings I use for most Laravel applications:

memory_limit = 256M
upload_max_filesize = 64M
post_max_size = 64M
max_execution_time = 300
max_input_vars = 3000
date.timezone = "Asia/Kolkata"
opcache.enable=1
opcache.memory_consumption=256
opcache.max_accelerated_files=20000
opcache.validate_timestamps=0

Memory Note: The memory limit might seem high, but Laravel applications can use quite a bit of memory, especially when processing large datasets or generating reports. I’ve seen applications fail with lower memory limits during peak usage.

Step 3: NGINX Installation and Optimization

Basic Installation

Installing NGINX is straightforward:

sudo apt install nginx -y

Production Optimization

The default configuration works but isn’t optimized for production Laravel applications. I always customize the main configuration:

sudo nano /etc/nginx/nginx.conf

Add these optimizations in the http block:

client_max_body_size 64M;
keepalive_timeout 65;
server_tokens off;

gzip_vary on;
gzip_min_length 1000;
gzip_proxied any;
gzip_comp_level 6;
gzip_types
    text/plain
    text/css
    text/xml
    text/javascript
    application/json
    application/javascript
    application/xml+rss
    application/atom+xml
    image/svg+xml;

Performance Benefits:

  • The server_tokens off directive hides the NGINX version number from HTTP headers (small security improvement)
  • The gzip configuration can reduce bandwidth usage by up to 70% for text-based content

Starting NGINX

sudo systemctl start nginx
sudo systemctl enable nginx
sudo systemctl status nginx

You should now be able to visit your server’s IP address and see the NGINX welcome page.

Security Configuration

Security setup comes next. I configure the firewall before installing anything else:

sudo ufw enable
sudo ufw allow ssh
sudo ufw allow 'Nginx Full'

Why this matters: The firewall configuration is crucial. I’ve seen servers get compromised within hours of being connected to the internet without proper firewall rules. Only allow the ports you actually need.

Step 4: Setting Up Composer and Laravel

Installing Composer Globally

Composer is essential for modern PHP development. I install it globally:

curl -sS https://getcomposer.org/installer | php
sudo mv composer.phar /usr/local/bin/composer
sudo chmod +x /usr/local/bin/composer

Verify it’s working:

composer --version

Laravel Installation Options

For a new project:

cd /var/www
sudo composer create-project laravel/laravel laravel-app --prefer-dist

For an existing project from Git:

cd /var/www
sudo git clone https://github.com/your-username/your-project.git laravel-app
cd laravel-app
sudo composer install --no-dev --optimize-autoloader

Optimization Tip: The –no-dev flag excludes development dependencies, making the installation smaller and more secure. The –optimize-autoloader flag generates optimized class maps that improve performance.

Frontend Dependencies (If Needed)

Many Laravel applications need Node.js for frontend compilation:

curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash -
sudo apt-get install -y nodejs
cd /var/www/laravel-app
sudo npm install
sudo npm run build

Step 5: Database Setup and Configuration

Installing MariaDB

I prefer MariaDB over MySQL for Laravel applications. MariaDB is fully compatible with MySQL but often performs better:

sudo apt install mariadb-server -y
sudo mysql_secure_installation

Security Note: The secure installation script asks several questions. I always remove anonymous users, disable remote root login, and remove the test database. These steps eliminate common security vulnerabilities.

Database and User Creation

Create a dedicated database and user:

sudo mysql -u root -p

In the MySQL console:

CREATE DATABASE laravel_app CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'laravel_user'@'localhost' IDENTIFIED BY 'your_secure_password';
GRANT ALL PRIVILEGES ON laravel_app.* TO 'laravel_user'@'localhost';
FLUSH PRIVILEGES;
EXIT;

Important: The utf8mb4 character set is important for proper Unicode support, including emojis.

Laravel Environment Configuration

Configure the Laravel environment:

cd /var/www/laravel-app
sudo cp .env.example .env
sudo nano .env

Update with production settings:

APP_NAME="Your Laravel App"
APP_ENV=production
APP_KEY=
APP_DEBUG=false
APP_URL=https://your-domain.com

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel_app
DB_USERNAME=laravel_user
DB_PASSWORD=your_secure_password

CACHE_DRIVER=file
QUEUE_CONNECTION=database
SESSION_DRIVER=file
LOG_CHANNEL=daily

Critical: Setting APP_DEBUG=false is crucial for production. Debug mode exposes sensitive information.

Application Setup

Generate the application key and run migrations:

sudo php artisan key:generate
sudo php artisan migrate --force
sudo php artisan config:cache
sudo php artisan route:cache
sudo php artisan view:cache

Performance Boost: The caching commands improve performance by pre-compiling configuration files.

Step 6: File Permissions and Security

Getting permissions right is critical for both security and functionality:

sudo chown -R www-data:www-data /var/www/laravel-app
sudo find /var/www/laravel-app -type f -exec chmod 644 {} \;
sudo find /var/www/laravel-app -type d -exec chmod 755 {} \;
sudo chmod -R 775 /var/www/laravel-app/storage
sudo chmod -R 775 /var/www/laravel-app/bootstrap/cache

These permissions allow Laravel to write logs and cache data while maintaining security.

Step 7: NGINX Configuration for Laravel

Creating the Site Configuration

Create the NGINX site configuration:

sudo nano /etc/nginx/sites-available/laravel-app

Production configuration:

server {
    listen 80;
    server_name your-domain.com www.your-domain.com;
    root /var/www/laravel-app/public;
    index index.php index.html;

    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;

    access_log /var/log/nginx/laravel-app_access.log;
    error_log /var/log/nginx/laravel-app_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.3-fpm.sock;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }

    location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
        expires 1y;
        add_header Cache-Control "public, immutable";
    }

    location ~ /\.(ht|env) {
        deny all;
    }
}

Enabling the Site

sudo ln -s /etc/nginx/sites-available/laravel-app /etc/nginx/sites-enabled/
sudo rm /etc/nginx/sites-enabled/default
sudo nginx -t
sudo systemctl reload nginx

Step 8: SSL Certificate with Let’s Encrypt

Never run production applications without HTTPS:

sudo apt install certbot python3-certbot-nginx -y
sudo certbot --nginx -d your-domain.com -d www.your-domain.com

Automatic Renewal Setup

Set up automatic renewal:

sudo crontab -e

Add:

0 12 * * * /usr/bin/certbot renew --quiet

Step 9: Monitoring and Maintenance

Log Rotation Setup

Set up log rotation to prevent disk space issues:

sudo nano /etc/logrotate.d/laravel-app

Add configuration:

/var/www/laravel-app/storage/logs/*.log {
    daily
    missingok
    rotate 52
    compress
    notifempty
    create 0640 www-data www-data
}

Backup Script Creation

Create a backup script:

sudo nano /usr/local/bin/backup-laravel.sh

Backup script content:

#!/bin/bash
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_DIR="/backups/laravel-app"
APP_DIR="/var/www/laravel-app"

mkdir -p $BACKUP_DIR

# Database backup
mysqldump -u laravel_user -p'your_secure_password' laravel_app > $BACKUP_DIR/database_$DATE.sql

# Application files backup
tar -czf $BACKUP_DIR/app_files_$DATE.tar.gz $APP_DIR --exclude=$APP_DIR/storage/logs

# Keep only last 7 days of backups
find $BACKUP_DIR -name "*.sql" -mtime +7 -delete
find $BACKUP_DIR -name "*.tar.gz" -mtime +7 -delete

Make it executable and schedule it:

sudo chmod +x /usr/local/bin/backup-laravel.sh
sudo crontab -e

Add the backup job:

0 2 * * * /usr/local/bin/backup-laravel.sh

Performance Optimization Tips

Redis for Caching

Install Redis for better caching performance:

sudo apt install redis-server -y
sudo systemctl enable redis-server

Update your Laravel .env file to use Redis:

CACHE_DRIVER=redis
SESSION_DRIVER=redis
QUEUE_CONNECTION=redis

Queue Workers with Supervisor (If needed)

Configure queue workers for background job processing. Install Supervisor:

sudo apt install supervisor -y

Create a worker configuration:

sudo nano /etc/supervisor/conf.d/laravel-worker.conf

Add configuration:

[program:laravel-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/laravel-app/artisan queue:work --sleep=3 --tries=3 --max-time=3600
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
user=www-data
numprocs=2
redirect_stderr=true
stdout_logfile=/var/www/laravel-app/storage/logs/worker.log

Start the workers:

sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl start laravel-worker:*

Database Optimization

For database optimization, tune MySQL settings:

sudo nano /etc/mysql/mariadb.conf.d/50-server.cnf

Add under [mysqld]:

innodb_buffer_pool_size = 1G
innodb_log_file_size = 256M
query_cache_size = 64M
max_connections = 300
innodb_flush_log_at_trx_commit = 2

Restart MySQL after configuration changes:

sudo systemctl restart mariadb

Troubleshooting Common Problems

Over the years, I’ve encountered the same issues repeatedly. Here are the most common problems and their solutions:

500 Internal Server Error

Usually means: Permission issues or missing environment variables

Solution: Check your error logs first:

sudo tail -f /var/log/nginx/laravel-app_error.log
sudo tail -f /var/www/laravel-app/storage/logs/laravel.log

Most 500 errors come from incorrect file permissions. Double-check that storage and bootstrap/cache directories are writable by the web server.

Database Connection Refused

Usually means: The database service isn’t running or credentials are wrong

Solution: Verify the database is running:

sudo systemctl status mariadb

Test the connection manually:

mysql -u laravel_user -p laravel_app

Slow Page Loading

Usually indicates: Caching issues or database problems

Solution: Clear all Laravel caches:

php artisan config:clear
php artisan cache:clear
php artisan route:clear
php artisan view:clear
composer dump-autoload --optimize

File Upload Issues

Usually relates to: PHP configuration limits

Solution: Check your upload_max_filesize and post_max_size settings in php.ini match your NGINX client_max_body_size setting.

Session Problems

Can occur when: Session files aren’t writable

Solution: Ensure the storage/framework/sessions directory has proper permissions:

sudo chmod -R 775 /var/www/laravel-app/storage/framework/sessions
sudo chown -R www-data:www-data /var/www/laravel-app/storage/framework/sessions

Security Hardening Checklist

Security should be a priority from day one. Here’s my security checklist for Laravel deployments:

Essential Security Steps

  • Keep all software updated regularly – Set up automatic updates for security patches but test major updates in staging first
  • Use strong passwords everywhere – Database passwords, application keys, and server passwords should all be randomly generated and stored securely
  • Configure the firewall – Only allow necessary ports (SSH, HTTP, HTTPS)
  • Disable unnecessary services – Fresh Ubuntu installations often have services running that aren’t needed
  • Set up fail2ban to prevent brute force attacks:
    sudo apt install fail2ban -y
    sudo systemctl enable fail2ban
  • Configure SSL properly – Use strong ciphers and HSTS headers. Let’s Encrypt certificates are free and automatically renewed
  • Hide server information – The server_tokens off directive in NGINX helps with this
  • Restrict access to sensitive files – Your .env file should never be accessible via web requests
  • Monitor logs regularly – Set up log monitoring or at least check logs weekly
  • Use environment variables – Never commit passwords or API keys to version control

Scaling and Performance Monitoring

As your application grows, you’ll need to monitor performance and potentially scale your infrastructure.

Monitoring Tools

Install htop for real-time server monitoring:

sudo apt install htop -y

Application Monitoring: Use Laravel Telescope for application-level monitoring in development, but disable it in production for security reasons.

Database Performance Monitoring

Set up database slow query logging to identify performance bottlenecks:

sudo nano /etc/mysql/mariadb.conf.d/50-server.cnf

Add:

slow_query_log = 1
long_query_time = 2
slow_query_log_file = /var/log/mysql/slow.log

Disk Space Monitoring

Monitor disk space usage regularly. Log files and uploads can fill up disks quickly:

df -h
du -sh /var/www/laravel-app/storage/logs/

Scaling Considerations

Consider implementing horizontal scaling when a single server isn’t enough. Load balancers, multiple application servers, and database replication become necessary for high-traffic applications.

Final Thoughts

This setup has served me well across dozens of production deployments. The combination of Ubuntu’s stability, NGINX’s performance, PHP 8.3’s modern features, and Laravel’s developer-friendly framework creates a solid foundation for web applications.

Key Takeaways

  • Deployment is just the beginning – Regular monitoring, security updates, and performance optimization are ongoing responsibilities
  • Keep your system updated – Monitor your logs, and don’t be afraid to adjust configurations based on your specific needs
  • Understand each component – Take time to learn the fundamentals rather than just copying configurations. This knowledge will help you troubleshoot issues and optimize performance as your application grows
  • Always test first – Test your deployment process in a staging environment before applying changes to production. I’ve learned this lesson the hard way more times than I care to admit

The Bottom Line

With proper setup and maintenance, this stack can handle everything from small personal projects to large enterprise applications. The reliability and performance you get from this combination make it worth the initial setup effort.

Related guides

Account services are unavailable until the production backend is configured.