NewHalo Edge 3 is here: HTTP/3 everywhere and 42 edge locations.Learn more
Docs / LAMP and app hosting

Deploy a PHP application

LAMP and app hosting9 min readUbuntu 24.04 LTS

Putting a PHP application on a server is more than copying files. The application's code, its secrets and its public pages should live in different places with different permissions. This guide shows a layout that keeps things tidy and protected, and an nginx configuration that serves only the public folder.

Before you begin

  • A working LEMP or LAMP stack (see Install a LEMP stack or Install a LAMP stack), with PHP 8.3.
  • A database and account for the app (see Create a database and a dedicated user).
  • Your application code on your computer, with a public/ folder that holds the files visitors can request.
  • SSH access as a sudo user. This guide uses alex.

Step 1: Create the application folder

Create the folder and give your user ownership, with the web server group (www-data) as the group. That lets you upload files and lets nginx read them without giving the web server write access to your code.

sudo mkdir -p /var/www/example.com/public
sudo chown -R alex:www-data /var/www/example.com

Step 2: Upload the application

From your own computer, copy the project. The rsync command below skips version-control data and any local configuration file, so secrets from your laptop do not reach the server.

rsync -av --exclude '.git' --exclude 'config/config.php' ./myapp/ alex@203.0.113.10:/var/www/example.com/

If your project is in a Git repository, you can instead run git clone on the server and then git pull for later updates.

Step 3: Add the configuration file outside the public folder

Store database credentials in a file that sits beside public/, not inside it. Because the web root is public/, nginx will never serve this file to visitors.

mkdir -p /var/www/example.com/config
nano /var/www/example.com/config/config.php
<?php
return [
    'db_host' => 'localhost',
    'db_name' => 'blog',
    'db_user' => 'blog_app',
    'db_pass' => 'PASTE_YOUR_PASSWORD_HERE',
];

Then limit who can read it. Only the owner and the web server group need access:

sudo chown alex:www-data /var/www/example.com/config/config.php
sudo chmod 640 /var/www/example.com/config/config.php

Step 4: Set safe permissions

Folders should be readable and enterable by the group, and files readable by the group but writable only by you. The find command applies these rules to everything in the application:

sudo find /var/www/example.com -type d -exec chmod 750 {} \;
sudo find /var/www/example.com -type f -exec chmod 640 {} \;

If the app needs to store uploads or cached files, create a folder for them and let the web server write there, and only there:

mkdir -p /var/www/example.com/storage/uploads
sudo chown -R www-data:www-data /var/www/example.com/storage
sudo chmod -R 770 /var/www/example.com/storage
Tip: Keep writable folders as few as possible. Every writable folder is a place an attacker could try to drop a file.

Step 5: Configure nginx for the public folder

Create a server block whose root is the public/ folder, and whose PHP block sends requests to PHP-FPM:

sudo nano /etc/nginx/sites-available/example.com
server {
    listen 80;
    server_name example.com www.example.com;

    root /var/www/example.com/public;
    index index.php index.html;

    location / {
        try_files $uri $uri/ /index.php?$is_args$args;
    }

    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/run/php/php8.3-fpm.sock;
    }

    location ~ /\.(?!well-known) {
        deny all;
    }
}

The try_files line sends unknown addresses to index.php, which many frameworks expect. The last block blocks hidden files such as .env and .git. If your app does not use pretty URLs, you can simplify the try_files line to try_files $uri $uri/ =404;.

Enable the site and reload:

sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/
sudo rm -f /etc/nginx/sites-enabled/default
sudo nginx -t
sudo systemctl reload nginx

On Apache, set DocumentRoot /var/www/example.com/public in the virtual host and add a <Directory /var/www/example.com/public> block with AllowOverride All if the app ships an .htaccess file.

Step 6: Test the deployment

curl -I http://example.com
HTTP/1.1 200 OK
Content-Type: text/html

Now confirm that a private file is not reachable. Because the web root is public/, this should return 404:

curl -I http://example.com/config/config.php
HTTP/1.1 404 Not Found

Troubleshooting

  • 502 Bad Gateway. PHP-FPM is not running or the socket path is wrong. Run sudo systemctl status php8.3-fpm and ls /run/php/.
  • 403 Forbidden. A folder lacks execute permission for the group, or the path in the server block is wrong. Re-run the permission commands in Step 4.
  • Blank page. The app is failing silently. Read the PHP and nginx logs with sudo tail -n 50 /var/log/nginx/error.log.
  • "Permission denied" when uploading files. The storage folder is not writable by www-data. Re-run the chown and chmod commands in Step 4.

Next steps

Add HTTPS so that logins and forms are encrypted, and set up regular backups of both the files and the database. Your application's own documentation will list any extra PHP extensions it needs.

Something not working? Chat or email our support team and we will help you get unstuck.

Contact support