Guide: How to Deploy PHP Application

First of all, I am writing this guide for myself. It should remind me how to deploy PHP applications correctly in the future because I don't do this too often. The last time I deployed an application was almost four years ago. 😲

This process is a bit frustrating for me, and I always try to find a few tutorials to recall what I should do step by step. I don't like to remember things that I don't need daily if I can just Google them or ask for help in ChatGPT.

But the biggest issue is that it is impossible to find one article that covers all the steps and things you should do. That's why I decided to collect all the information I found, along with some notes from myself and a few comments. I hope it will be useful not only for me; that's why this post will be public.

Let's start

In the beginning, you should have your PHP application set up locally; it should be ready, or in other words, it should work. I would also recommend creating a GIT repository and pushing your changes to the remote branch if you haven't done this yet.

Okay, you have it 😎

To deploy your app, you need to purchase a server and a domain. If you bought the server in one place and the domain in another, you will need to connect them to each other.

How to connect domain and server?

If you bought the server and domain in one registry, then you can just skip this section and move forward.

One thing you should understand is that the domain should point to the server, not the other way around. That's why we need to copy some information from the server and use it on the domain.

There are two options for how you can connect them.

  • Use DNS NS Records  

You can find this information from your server provider. For example, on my server, these records look like this:

ns1.digitalocean.com.

ns2.digitalocean.com.

ns3.digitalocean.com.

And paste these records into your domain provider. This way, you give full control over the domain to the server. I usually prefer this option, but there is also another one.

Use IP Address Records

You can copy the IP address of your server, then add an A record on your domain site and paste this value there. This will connect your domain name with your server's IP address.

 

These changes to the domain will not be applied immediately. Sometimes it can take more than 12 hours, but usually less than 15 minutes. To check if the changes were applied correctly, you can visit a site like https://dnschecker.org/all-dns-records-of-domain.php. There, you should see that the A record or NS records have changed from the default to yours.

In the next step, you will need to configure your server. My server is on Ubuntu, so my command examples may not work on your side, but you can always find a replacement. The main point is to understand the idea.

Configure a server

First of all, you will access your server through SSH. Usually, you need a password to log in, but I prefer using an SSH key and recommend this to you.

You can read how to create an SSH key on your machine here https://docs.gitlab.com/ee/user/ssh.html#ed25519-ssh-keys

Then, you should copy your public key and paste it on the server side into the ~/.ssh/authorized_keys file. If you don't have a .ssh folder on your server, you will need to generate it using the same commands you used on your local machine.

The first user you log in to on the server is the root user. It's better to create a new regular user for security and permission reasons. You can read how to do this in this guide https://www.digitalocean.com/community/tutorials/initial-server-setup-with-ubuntu.

Install NGINX on your server

At this step, you have a configured server. Let's start installing the necessary applications.

The first one is NGINX. I hope you know why you need NGINX instead of Apache.

You can read how to install NGINX here https://www.digitalocean.com/community/tutorials/how-to-install-nginx-on-ubuntu-22-04

But there are a few things that I'd like to emphasize ☝️

1. Path to your project

You don't need to have this exact path for your application

/var/www/your_domain/html/

Usually, the project is located directly after www or at least after your domain. However, the html folder is redundant, in my opinion. Don't forget to use the correct path to your project in the NGINX configuration (server block).

2. Permissions

At this step, it’s a good approach to configure your file permissions. There are three types of files:

  • Publicly accessible (like HTML, CSS, JS) – they should be readable and executable for NGINX.

If you have any directories in the public folder that you use for uploads, they should also be writable.

chmod -R u=rwx,g=rwx,o=rwx <path_to_project>/public

Logs and cache files – they should be readable, executable and writable for NGINX.

chmod -R u=rwx,g=rwx,o=rwx <path_to_project>/var<path_to_project>/public

Configuration files – they should be accessible only to the owner and their group.

chmod u=rw,g=rw,o=r <path_to_project>/.env

P.S. Replace <path_to_project> with your actual path.

Let's Encrypt your domain with NGINX

It's a must to encrypt the connection (have an HTTPS certificate) between the server and your users nowadays.

Before encrypting your connection, decide for yourself: β€œDo you want to have both versions, www and non-www, or just one of them?” I prefer to have both, but I make one version the main one and configure a redirect for the second. If you have the same preference as I do, then add an A record for the main domain version and a CNAME record for the second version. In my case, it looks like this.

And do this before following the next guide.

In this article, you can read how to protect your connection https://www.digitalocean.com/community/tutorials/how-to-secure-nginx-with-let-s-encrypt-on-ubuntu-22-04

I’m almost sure that you will not have the certbot package installed by default. That's why you need to run this command to install it.

sudo snap install --classic certbot

Configure redirects on NGINX

As I have two versions, I need to configure redirectsβ€”specifically, two redirects: from HTTP to HTTPS and from www to non-www.

  • http://www.vitaliisheverov.com/ β†’ https://vitaliisheverov.com/
  • http://vitaliisheverov.com/ β†’ https://vitaliisheverov.com/

I have configured these redirects with this code. Do you agree that it looks much simpler than doing the same in Apache's .htaccess? πŸ€”

server {
	if ($host = www.vitaliisheverov.com) {
		return 301 https://vitaliisheverov.com$request_uri;
	}
	
	if ($host = vitaliisheverov.com) {
    	return 301 https://$host$request_uri;
	}
    listen 80;
    listen [::]:80;
	server_name vitaliisheverov.com www.vitaliisheverov.com;
	return 404;
}

Then, I will achieve the redirect from https://www.vitaliisheverov.com/ to https://vitaliisheverov.com/ (from www to non-www) with this code.

server {
	root /var/www/vitaliisheverov/html/public;
	index index.php index.html;
    server_name vitaliisheverov.com www.vitaliisheverov.com;
    if ($host = www.vitaliisheverov.com) {
            return 301 https://vitaliisheverov.com$request_uri;
    }
    
    ...

To check that the changes have been applied, you can use Postman or any online tool, as your browser might cache previous responses and not show you the correct results, even if they are configured correctly.

How to install MySQL on server

The next step for me was to install the database. Here, I will add information about MySQL, and maybe later I will update it with PostgreSQL.

You can read the step-by-step instructions here https://www.digitalocean.com/community/tutorials/how-to-install-mysql-on-ubuntu-22-04. 

Honestly, I respect people who can easily work without a UI, just in the terminal, but I'm not one of them.

I like UI, which is why I manage my database via the desktop application DBeaver. It's free and does a lot for me.

To connect from DBeaver to my remote database:

  • Use the SSH connection as a tunnel.
  • Add your private SSH key to the connection settings in the SSH tab.
  • Use the database username, password, and host as localhost because you are already on the server via SSH.

Configure NGINX for PHP application

1. Slugs

I will use the slug feature on my site in the URI, and to make it work, I need to extend my NGINX server block location section with this line.

try_files $uri $uri/ /index.php?q=$request_uri;

instead of this line try_files $uri $uri/ =404;

2. Public directory

My index.php entry point file is located in the public directory, not in the root directory. This is common for modern frameworks like Laravel or Symfony. That's why I needed to modify my server block (in /etc/nginx/sites-available) in this way:

server {
        root /var/www/<your_site>/public;

Then, reload NGINX with the command to make the new settings work.

sudo systemctl reload nginx

Install application

To have a secure connection between my server and the remote repository, I configured an SSH connection. To do this, I added my server's public SSH key to the GitLab repository. To achieve the same, you need to copy your SSH key and go to Profile Settings β†’ SSH Keys β†’ Add New Key, then paste your key as the value.

Let's pull our project to the server

cd /var/www/<your_project_name>
git init
git remote add origin <your_repository_ssh_path>
git pull origin <main_branch>

Then we should configure our ENV. Modern frameworks do a lot under the hood and try to find and resolve everything automatically. In my case, I only needed to set up the database connection and specify the main domain name. For this, I created a file called .env.local. See an example of the content below:

DATABASE_URL="mysql://<db_user>:<db_password>@localhost:3306/<db_name>"
APP_URL=https://vitaliisheverov.com

Pay attention that I removed the classic Symfony parameter for the database serverVersion. I don't use any specific database features, so I don't need to specify this parameter.

Quick reminder ☝️: If you use the wrong database version, you might see an exception when you run migrations or manipulate the database.

Column not found: 1054 Unknown column 'i_c.TABLE_NAME' in 'where clause'

One more thing you should remember ☝️: Your application can use different PHP libraries locally, and they should also be installed on your server. To avoid forgetting to install them and to prevent breaking your application with an update, add your PHP extensions to the composer.json.

For example, I need the xml extension, and to check that this extension is installed, I just need to add its alias to the composer require section.

"require": {
   "ext-xml": "*",
   "ext-iconv": "*",
   ...
}

If the ext-xml extension is not installed, it will show you an error like this:

- symfony/framework-bundle v6.4.12 requires ext-xml * -> it is missing 
from your system. Install or enable PHP's xml extension.

To install ext-xml, I just run this command:

sudo apt install php-xml

Okay, now we are ready to install our application.

Run the command:

composer install

Then, run the migrations to have an up-to-date database schema.

php bin/console doctrine:migrations:migrate

You can optimize the ENV file processing in Symfony with this command:

composer dump-env prod

It will create a new PHP file based on your ENV, and this file will be used by Symfony.

But never change this file manually; always edit .env.local and then rerun the command above to avoid missing important and secure information.

How about node modules?

Have you noticed that I didn't mention the command npm install?

And that's super cool! You don't need to install node_modules on your server. Modern JS builders like Webpack build your assets into a few small JS files with one or more entry points. These files contain everything you need in a compressed format. 😎

Also, building npm assets on a production server is not recommended because it can be a heavy operation. That's why this is usually done elsewhere and then just copied. But if it's your personal small project, like mine, you can easily build your assets locally and then copy your built assets, usually located in /public/build, to your server destination. For this, you can use rsync or at least FileZilla.

 

Congrats to yourself πŸ‘ πŸ‘ πŸ‘ Your site should be working now.

If you have any questions or advice, you can contact me on Twitter, and I will be happy to respond to you.

Share:

I would be happy to get your share 😊

Thanks for watching and reading me

My name is Vitalii Sheverov, and I'm writing tech posts, making YouTube videos and conduct personal trainings

If you have any questions you contact me on any social network