GICT knowledge base
How to Run Secure Laravel Apps on Ubuntu Using NGINX and PHP 8.3
Published
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
How to Increase PHP Upload Max Filesize on Apache and Nginx
Learn how to increase PHP upload max filesize on Apache and Nginx, including configuration settings, common issues, and approaches for handling large file uploads.
PHPHow TO: Debugging 500 errors on zencart
Troubleshooting Your Zen Cart: A Quick Guide to Debugging Running an e-commerce site on InterServer’s high-performance hosting gives you a great head start, but even the...
PHPPHP Versions on cPanel: Everything You Need to Know
PHP Versions on cPanel: Everything You Need to Know If you are running a PHP site here at InterServer, you have probably seen notifications in your dashboard talking...
PHPHow to Install or Upgrade to PHP8.4 in Ubuntu
Upgrade to PHP 8.4 on Ubuntu 24.04 for real-world speed boosts, better error handling, and smoother Laravel, WordPress, and Symfony support.
