Few considerations before starting to setup the server.
- Choose Ubuntu LTS as operating system.
- Assign a flexible IP to server if possible
- It will remain available to you even if you delete the server, ensuring consistent connectivity if you plan to switch servers, as you can reassign the same IP without changing DNS.
- Point the domain/sub-domain to server static IP, as it can take some time.
- Run
apt update
before installing packages ensures that your server's package list is up-to-date. This means when you install or upgrade software, you get the latest available versions and security patches. Without updating, you might install outdated or vulnerable packages, which could cause compatibility issues or security risks. - It's recommended to use public key authentication over passwords for server access. Public keys are far more secure, as they rely on cryptographic pairs, making it much harder for attackers to break in. By using keys, you can also disable password-based logins entirely, reducing exposure to brute-force attacks and phishing attempts. This is a simple but effective way to improve server security.
Before we begin the deployment process, let's set up proper user accounts for enhanced security.
This guide use dev name as super user if you want a different name, you will have to modify the commands.
-
Create a superuser named 'dev':
sudo adduser dev sudo usermod -aG sudo dev
Why: Creating a superuser separate from the root account adds an extra layer of security. This user can perform administrative tasks without logging in as root.
If you want to disable root login, you can set
PermitRootLogin no
in/etc/ssh/sshd_config
and restart the SSH service withsudo systemctl restart ssh
.If dev want to use public key for login.
Create
.ssh
directory and set permissions:sudo mkdir -p /home/dev/.ssh sudo touch /home/dev/.ssh/authorized_keys sudo chown -R dev:dev /home/dev/.ssh sudo chmod 700 /home/dev/.ssh sudo chmod 600 /home/dev/.ssh/authorized_keys
Why: These commands create the necessary
.ssh
directory andauthorized_keys
file, set the correct ownership, and apply the appropriate permissions to ensure secure SSH key-based authentication.To add your public key to the
authorized_keys
file, use the following command:echo "your-public-key" | sudo tee -a /home/dev/.ssh/authorized_keys
Replace
your-public-key
with your actual SSH public key.Why: This appends your public key to the
authorized_keys
file, allowing you to authenticate using your SSH key.If you decide to use public key authentication for the 'dev' user, you should disable password login for this specific user to enhance security. To do this, follow these steps:
-
Create a new SSH configuration file with higher priority:
sudo nano /etc/ssh/sshd_config.d/10-disable-passwords.conf
-
Add the following content to disable password authentication for all users except root:
PasswordAuthentication no Match User root PasswordAuthentication yes
-
Save the file and exit the editor.
-
Restart the SSH service to apply the changes:
sudo systemctl restart ssh
Why: Disabling password authentication for the 'dev' user ensures that only users with the correct SSH key can log in, significantly reducing the risk of brute-force attacks. Creating a separate configuration file with a higher priority ensures that your custom settings are applied without modifying the main SSH configuration file. This approach is cleaner and makes it easier to manage SSH settings.
-
-
Create an app-specific user: (Choose name as your app)
sudo adduser app-user --disabled-password
Why: Using an app-specific user further isolates the application, minimizing potential damage if the account is compromised.
Also add the user to www-data group.
sudo usermod -aG www-data app-user
-
Switch to the dev user:
su - dev
From this point on, perform all operations as the 'dev' user for app specific tasks unless specified otherwise.
-
Update and upgrade your Ubuntu server:
sudo apt update && sudo apt upgrade
Why: This ensures your system has the latest security patches and software versions.
-
Install required packages:
sudo apt install php-fpm php-mysql php-mbstring php-xml php-curl php-cli php-bcmath php-zip php-gd php-intl -y
Above command also includes installing PHP, if your app requires different php version you have to adjust it.
Why: These packages are essential for running a Laravel application.
-
Install and configure MySQL:
sudo apt install mysql-server -y sudo mysql_secure_installation
This will prompt you some questions answer it according to your needs.
Why: This installs MySQL and runs a security script to remove insecure defaults.
-
Create a database and user for your Laravel application:
sudo mysql
Then in the MySQL prompt:
CREATE DATABASE laravel_db; CREATE USER 'laravel_user'@'localhost' IDENTIFIED BY 'your_strong_password_here'; GRANT ALL PRIVILEGES ON laravel_db.* TO 'laravel_user'@'localhost'; FLUSH PRIVILEGES; EXIT;
Why: This sets up a dedicated database and user for your Laravel application, enhancing security and organization.
-
Install Nginx
sudo apt install nginx -y
-
Installing composer (Instructions copied from Download Composer)
- Download composer setup
php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
- Verify the installer SHA-384
php -r "if (hash_file('sha384', 'composer-setup.php') === 'dac665fdc30fdd8ec78b38b9800061b4150413ff2e3b6f88543c636f7cd84f6db9189d43a81e5503cda447da73c7e5b6') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;"
- Install composer
sudo php composer-setup.php --filename=composer --install-dir=/usr/local/bin
- Download composer setup
-
Set up the directory structure for Envoy:
git clone https://github.com/laravel/laravel.git sudo mkdir -p /var/www/your-app sudo mkdir -p /var/www/your-app/releases cp -Ra laravel/storage/ /var/www/your-app/storage rm -rf laravel
Why: This creates a directory structure suitable for zero-downtime deployments with Laravel Envoy.
It is a good structure for the app even if you are not using envoy.
-
Set proper permissions:
sudo chown -R app-user:www-data /var/www/your-app sudo chmod -R 755 /var/www/your-app sudo chmod -R 775 /var/www/your-app/storage
755 permissions for a file or directory mean that the owner has full read, write, and execute permissions, while other users and groups can only read and execute the file or directory.
Why: This ensures the app-specific user and web server have the necessary permissions.
-
Clone your Laravel application into a new release directory:
sudo su - dev git clone git@github.com:username-here/repo-name-here.git /var/www/your-app/releases/$(date +%Y_%m_%d_%H)
Why: This clones your application into a timestamped directory, allowing for easy rollbacks if needed.
-
Create a symlink to the latest release:
sudo -u dev ln -fns /var/www/your-app/releases/$(ls -t /var/www/your-app/releases | head -1) /var/www/your-app/current
Why: This creates a symlink named 'current' pointing to the latest release, which Nginx will serve.
-
Install dependencies:
cd /var/www/your-app/current sudo -u app-user composer install --no-dev sudo -u app-user chmod -R 775 /var/www/your-app/current/bootstrap/cache
Why: This installs the required PHP packages for your application, excluding development dependencies.
-
Set up environment file:
sudo -u app-user cp .env.example .env sudo -u app-user php artisan key:generate
Why: This creates your .env file and generates a unique application key for encrypting data.
-
Configure your
.env
file:sudo -u app-user nano .env
Update the following lines:
APP_ENV=production APP_DEBUG=false APP_URL=http://your-domain.com DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=3306 DB_DATABASE=laravel_db DB_USERNAME=laravel_user DB_PASSWORD=your_strong_password QUEUE_CONNECTION=database
Why: This configures your application with the correct environment settings and database credentials.
-
Run migrations:
sudo -u app-user php artisan migrate
Why: This sets up your database schema.
-
Optimize Laravel:
sudo -u app-user php artisan config:cache sudo -u app-user php artisan route:cache sudo -u app-user php artisan view:cache
Why: These commands cache configuration, routes, and views, improving application performance.
-
Create a new systemd service file:
sudo nano /etc/systemd/system/laravel-worker@.service
-
Add the following content:
[Unit] Description=Laravel Queue Worker %I After=network.target [Service] User=app-user Group=www-data Restart=always ExecStart=/usr/bin/php /var/www/your-app/current/artisan queue:work --queue=default --sleep=3 --max-time=3600 [Install] WantedBy=multi-user.target
Why: This creates a template service that can be used to start multiple queue workers. The
%I
in the description will be replaced with the instance name. -
Start and enable three worker services:
sudo systemctl enable laravel-worker@{1..3} sudo systemctl start laravel-worker@{1..3}
Why: This starts and enables three separate queue worker processes, allowing for parallel processing of queue jobs.
-
Check the status of the workers:
sudo systemctl status laravel-worker@{1..3}
Why: This allows you to verify that all three workers are running correctly.
For more information on Laravel queues and workers, refer to the official Laravel documentation on queues.
For more details on using systemd, you can check the systemd documentation.
-
Open the crontab for the app-user user:
sudo crontab -u app-user -e
-
Add the following line to run the Laravel scheduler every minute:
* * * * * php /var/www/your-app/artisan schedule:run >> /dev/null 2>&1
Why: This allows Laravel to run scheduled tasks, which is necessary for many Laravel applications.
- Create a new site configuration file:
sudo nano /etc/nginx/sites-available/your-app
- Add the following configuration (adjust as needed, copied from laravel docs):
server {
listen 80;
listen [::]:80;
server_name example.com;
root /srv/example.com/public;
add_header X-Frame-Options "SAMEORIGIN";
add_header X-Content-Type-Options "nosniff";
index index.php;
charset utf-8;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location = /favicon.ico { access_log off; log_not_found off; }
location = /robots.txt { access_log off; log_not_found off; }
error_page 404 /index.php;
location ~ \.php$ {
fastcgi_pass unix:/var/run/php/php8.2-fpm.sock;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
include fastcgi_params;
fastcgi_hide_header X-Powered-By;
}
location ~ /\.(?!well-known).* {
deny all;
}
}
Above code also specifies php socket unix:/var/run/php/php8.2-fpm.sock
adjust it according to your php version. php -v
Why: This configuration tells Nginx how to handle requests for your Laravel application, including routing PHP requests to PHP-FPM.
-
Enable the site:
sudo ln -s /etc/nginx/sites-available/your-app /etc/nginx/sites-enabled/
Why: This creates a symlink in the 'sites-enabled' directory, activating the configuration.
-
Test and reload Nginx:
sudo nginx -t sudo systemctl reload nginx
Why: This checks for configuration errors and applies the new settings.
To find your server's public IPv4 address, you can use one of the following methods:
-
From within the server (SSH into your server first):
curl -4 icanhazip.com
or
dig +short myip.opendns.com @resolver1.opendns.com
-
From your VPS provider's control panel: Most VPS providers display the server's IP address in their control panel or dashboard.
Why: You need the server's public IP address to configure DNS settings and to connect to your server.
To point a subdomain to your server:
-
Log in to your domain registrar's website or DNS management interface.
-
Create a new A record:
- Type: A
- Name: Your subdomain (e.g., 'app' for app.yourdomain.com)
- Value: Your server's public IPv4 address
- TTL: Can be set to automatic or 3600 seconds (1 hour)
-
Save the changes and wait for DNS propagation (can take up to 48 hours, but often much quicker).
Why: This tells the internet to direct traffic for your subdomain to your server's IP address.
To verify DNS propagation:
dig +short your-subdomain.your-domain.com
This should return your server's IP address once propagation is complete.
After setting up your subdomain, update your Nginx configuration to recognize it:
-
Edit your Nginx configuration file:
sudo nano /etc/nginx/sites-available/your-app
-
Update the
server_name
directive:server_name your-subdomain.your-domain.com;
-
Test and reload Nginx:
sudo nginx -t sudo systemctl reload nginx
Why: This ensures Nginx correctly handles requests to your subdomain.
After setting up your domain and Nginx configuration, it's crucial to secure your site with HTTPS. We'll use Certbot to obtain and install a free SSL certificate from Let's Encrypt.
-
Install Certbot and its Nginx plugin:
sudo apt update sudo apt install certbot python3-certbot-nginx -y
Why: Certbot automates the process of obtaining and installing SSL certificates.
-
Obtain and install the certificate:
sudo certbot --nginx -d your-subdomain.your-domain.com
Replace
your-subdomain.your-domain.com
with your actual domain.Why: This command tells Certbot to use the Nginx plugin, obtain an SSL certificate, and automatically configure Nginx to use it.
-
Follow the prompts:
- Enter your email address for important notifications.
- Agree to the terms of service.
- Choose whether to redirect HTTP traffic to HTTPS (recommended for most cases).
-
Verify the Nginx configuration:
sudo nginx -t sudo systemctl reload nginx
Why: This ensures that the new SSL configuration is correct and applies the changes.
-
Test automatic renewal:
sudo certbot renew --dry-run
Why: Certbot sets up a renewal timer, but this test ensures it's working correctly.
After successful installation, your Nginx configuration will be updated to use the new SSL certificate, and your site will be accessible via HTTPS.
Now that you have HTTPS set up, update your Laravel .env
file to use HTTPS:
APP_URL=https://your-subdomain.your-domain.com
Also, if you're using any Laravel features that generate URLs (like queue workers or scheduled tasks), you may need to set the following:
ASSET_URL=https://your-subdomain.your-domain.com
Why: This ensures that Laravel generates HTTPS URLs when needed.
- Laravel Deployment Best Practices
- Nginx Documentation
- Ubuntu Server Guide
- PHP-FPM Configuration
- MySQL Documentation
- Laravel Envoy Documentation
- DigitalOcean's DNS documentation (useful even if you're not using DigitalOcean)
- Nginx Server Names documentation
- Certbot Documentation
- Let's Encrypt Documentation
- SSL Labs Server Test - Use this to check your SSL configuration
Remember that Let's Encrypt certificates are valid for 90 days, but Certbot sets up an automatic renewal process. You can manually renew the certificate at any time using:
sudo certbot renew
Ensure that your firewall allows HTTPS traffic (usually port 443) if you have one configured.