Finally! After all those preparations, we can see our project working in our browser.
Usually, NginX and PHP-FPM service runs as www-data user and it is a system user for services.
In the best case scenario, we would like to isolate our web project from any system services and maybe have a different directory for example /home/web/demoproject
as opposed to /var/www/html
where you need root user explicitly.
- To add a new user enter the
adduser web
command as root. You will be prompted to define a password for a web user add fill in optional details. Make sure to choose a secure password.
root@ip-172-31-44-101:~# adduser webAdding user `web' ...Adding new group `web' (1001) ...Adding new user `web' (1001) with group `web' ...Creating home directory `/home/web' ...Copying files from `/etc/skel' ...New password:Retype new password:passwd: password updated successfullyChanging the user information for webEnter the new value, or press ENTER for the default Full Name []: Room Number []: Work Phone []: Home Phone []: Other []:Is the information correct? [Y/n] Y
- Now we can log in with a
web
user and create a structure for how we want our future laravel project served.
We can easily do that by entering sudo su web
or just su web
if you're a root. The user you're currently logged in as can be seen in your command prompt or optionally can be checked with the whoami
command.
ubuntu@ip-172-31-44-101:~$ sudo su webweb@ip-172-31-44-101:/home/ubuntu$ whoamiweb
Navigate to your home directory, this can be done by entering cd
without any parameters or cd ~
or cd /home/web
. The present working directory can be checked with the pwd
command.
web@ip-172-31-44-101:/home/ubuntu$ cdweb@ip-172-31-44-101:~$ pwd/home/web
Now create a new directory for our demo project:
web@ip-172-31-44-101:~$ mkdir demoproject
And for this step, we just create a single PHP file to test future configurations.
web@ip-172-31-44-101:~$ cd demoproject/web@ip-172-31-44-101:~/demoproject$ nano index.php
Here are the contents of our index.php
file for now.
<?php echo 'test';
Then press CTRL-X to save and exit the editor.
- Now we have a new user for our project and directory with some test files.
Currently, NginX and PHP-FPM would have no access to our /home/web
directory, and we need a few more things to do for NginX to be able to serve and process this php file.
- Add the user to the
www-data
group. Current present groups our new users are in can be checked with thegroups web
command.
root@ip-172-31-44-101:~# groups webweb : web
To add our user to the www-data
group. we can use the usermod
command with -aG
flags.
root@ip-172-31-44-101:~# usermod -aG www-data web
And then check groups again.
root@ip-172-31-44-101:~# groups webweb : web www-data
As we can see, the user has been added to the www-data group.
- Change
/home/web
folder permissions to allow services to read it using thechmod 755 /home/web
command.
web@ip-172-31-44-101:~$ chmod 755 /home/web
- Edit NginX config
root@ip-172-31-44-101:/home/web# nano /etc/nginx/sites-enabled/default
Lines that we are interested in are near each other starting with root /var/www/html
It should be the 41st and 44th lines, they look like that:
root /var/www/html; # Add index.php to the list if you are using PHPindex index.html index.htm index.nginx-debian.html;
Change root directive root /var/www/html
to root /home/web/demoproject
.
And as you have guessed the comment suggests we need to append index.php to the index directive:
Update index index.html index.htm index.nginx-debian.html;
to index index.html index.htm index.nginx-debian.html index.php;
.
The result should look like that:
root /home/web/demoproject; # Add index.php to the list if you are using PHPindex index.html index.htm index.nginx-debian.html index.php;
Full NginX default site configuration without comments looks like this:
server { listen 80 default_server; listen [::]:80 default_server; root /home/web/demoproject; index index.html index.htm index.nginx-debian.html index.php; server_name _; location / { try_files $uri $uri/ =404; } location ~ \.php$ { fastcgi_pass unix:/var/run/php/php8.1-fpm.sock; fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name; include fastcgi_params; }}
For sake of simplicity, we won't go deeper into NginX configuration nuances.
- Another config we need to update is the PHP-FPM pool. To be able to process php files we need to change the user and group php-fpm process pool is running on. It is possible to configure another pool, but now let's just update the default one. This can be done by editing
/etc/php/8.1/fpm/pool.d
.
root@ip-172-31-44-101:~# nano /etc/php/8.1/fpm/pool.d/www.conf
In the [www]
section find lines user = www-data
and group = www-data
, they should not be too far from the beginning.
From
user = www-datagroup = www-data
update it to
user = webgroup = web
And exit by saving the file with CTRL-X.
- For changes to take effect it is necessary to restart NginX and PHP-FPM. Let's proceed with
systemctl
command from previous chapters.
root@ip-172-31-44-101:~# systemctl restart nginxroot@ip-172-31-44-101:~# systemctl restart php8.1-fpm
If you navigate to your site URL http://<YOUR-SERVER-IP-ADDRESS>
you should see the site echoing test
to confirm the configuration of services is successful.
- From this point there are several options for how to populate your Laravel project files into our new
/home/web/demoproject
directory. The simplest form would be to download your Laravel project archive on the server and extract it to the/home/web/demoproject
directory or pull it from the GIT repository.
We will cover how to pull it from your GitHub repository by using the git clone
command.
Since we will be cloning the existing repository we can delete the existing demoproject
directory, because it will be created when we clone the Laravel project.
web@ip-172-31-44-101:~$ rm -fr demoproject/
To be able to clone
the repository and later pull
from it using the ssh method we need to generate a new ssh-key pair.
Note: even if the repository is public, you need to add your ssh key otherwise access would be denied.
Note: If you wish to clone the public repository you do not own, we suggest skipping a key generation and using the HTTPS method. For example, to copy the demo project we used in this tutorial, use
git clone https://github.com/mc0de/rds-ec2-project.git demoproject
and proceed to step 10.
To generate new ssh-key pair use the ssh-keygen
command.
Generating public/private ed25519 key pair.Enter file in which to save the key (/home/web/.ssh/id_ed25519):Enter passphrase (empty for no passphrase):Enter same passphrase again:Your identification has been saved in /home/web/.ssh/id_ed25519Your public key has been saved in /home/web/.ssh/id_ed25519.pubThe key fingerprint is:SHA256:/B0X+6/9X83V+PXvXGTIVYUTSPUjNSp8PMwBC+rvwvU [email protected]The key's randomart image is:+--[ED25519 256]--+| . oo+o=+|| . o * *.o|| . + O.oo|| .. o.o*o|| .S . * *|| .o . o *=|| . ..o . O|| o. E o=|| .. .o@|+----[SHA256]-----+
Your public key can be previewed using this command:
web@ip-172-31-44-101:~$ cat /home/web/.ssh/id_ed25519.pub
Now copy this key, go to your account settings on GitHub https://github.com/settings/keys and add it by pressing the [New SSH Key]
button
Fill in the form and submit it by pressing [Add SSH key]
.
Now go to your repository and copy the SSH URL:
And clone the repository:
Cloning into 'demoproject'...remote: Enumerating objects: 173, done.remote: Counting objects: 100% (173/173), done.remote: Compressing objects: 100% (130/130), done.remote: Total 173 (delta 26), reused 173 (delta 26), pack-reused 0Receiving objects: 100% (173/173), 119.98 KiB | 706.00 KiB/s, done.Resolving deltas: 100% (26/26), done.
The last argument of the git clone [email protected]:mc0de/rds-ec2-project.git demoproject
command is the directory where it should be stored otherwise it will use the GitHub repository name which is not always ideal.
- Up until this moment, a very simple NginX configuration was enough to test a single PHP file and our project isolation, but as you know Laravel is a lot more, and doesn't even have
index.php
in its root directory. So we need to update our NginX configuration once again. According to Official Documentation https://laravel.com/docs/9.x/deployment#nginx our configuration now should look like that:
root@ip-172-31-44-101:~# nano /etc/nginx/sites-enabled/default
server { listen 80 default_server; listen [::]:80 default_server; root /home/web/demoproject/public; add_header X-Frame-Options "SAMEORIGIN"; add_header X-Content-Type-Options "nosniff"; index index.php; charset utf-8; server_name _; 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.1-fpm.sock; fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name; include fastcgi_params; } location ~ /\.(?!well-known).* { deny all; }}
As we see our document root is now /home/web/demoproject/public
, index directive has only an index.php
value because this is the only entry point the Laravel application has and a bunch of other settings. More information on various configurations can be found on Official NginX Documentation https://www.nginx.com/resources/wiki/start/#pre-canned-configurations.
- From this point road will be a lot less bumpy. The hardest part in the past. It is time to set up the Laravel project using the usual steps you do in your development environment.
Navigate to your project directory:
web@ip-172-31-44-101:~$ cd demoproject/
Copy .env.example
to .env
web@ip-172-31-44-101:~/demoproject$ cp .env.example .env
Fill in your database credentials in the .env
file when we were settings the RDS instance in the previous chapter.
web@ip-172-31-44-101:~/demoproject$ nano .env
DB_HOST=database-1.cbd1u2cfua0q.eu-central-1.rds.amazonaws.comDB_DATABASE=demo_projectDB_USERNAME=demo_userDB_PASSWORD=******
Run composer install
web@ip-172-31-44-101:~/demoproject$ composer installInstalling dependencies from lock file (including require-dev)Verifying lock file contents can be installed on current platform.Package operations: 108 installs, 0 updates, 0 removalsAs there is no 'unzip' nor '7z' command installed zip files are being unpacked using the PHP zip extension.This may cause invalid reports of corrupted archives. Besides, any UNIX permissions (e.g. executable) defined in the archives will be lost.Installing 'unzip' or '7z' may remediate them. - Downloading doctrine/inflector (2.0.6) - Downloading doctrine/lexer (1.2.3)<...> INFO Discovering packages. laravel/breeze .............................................................................................................................. DONE laravel/sail ................................................................................................................................ DONE laravel/sanctum ............................................................................................................................. DONE laravel/tinker .............................................................................................................................. DONE nesbot/carbon ............................................................................................................................... DONE nunomaduro/collision ........................................................................................................................ DONE nunomaduro/termwind ......................................................................................................................... DONE spatie/laravel-ignition ..................................................................................................................... DONE 81 packages you are using are looking for funding.Use the `composer fund` command to find out more!
Run php artisan key:generate
web@ip-172-31-44-101:~/demoproject$ php artisan key:generate INFO Application key set successfully.
Run php artisan storage:link
to make necessary symlinks for accessing files in public storage
web@ip-172-31-44-101:~/demoproject$ php artisan storage:link INFO The [public/storage] link has been connected to [storage/app/public].
Run migrations using php artisan migrate
web@ip-172-31-44-101:~/demoproject$ php artisan migrate INFO Preparing database. Creating migration table ............................................................................................................... 58ms DONE INFO Running migrations. 2014_10_12_000000_create_users_table ................................................................................................... 78ms DONE 2014_10_12_100000_create_password_resets_table ......................................................................................... 34ms DONE 2019_08_19_000000_create_failed_jobs_table ............................................................................................. 40ms DONE 2019_12_14_000001_create_personal_access_tokens_table .................................................................................. 62ms DONE
- To later update your repository on the server after you pushed some changes:
Login to server:
$ ssh -i ~/.ssh/ec2-demo-web-ubuntu-server.pem ubuntu@ubuntu-aws
Login as a web
user:
ubuntu@ip-172-31-44-101:~$ sudo su web
Navigate to your project's directory:
web@ip-172-31-44-101:/home/ubuntu$ cd /home/web/demoproject/
Issue git pull
command:
web@ip-172-31-44-101:~/demoproject$ git pullremote: Enumerating objects: 12, done.remote: Counting objects: 100% (12/12), done.remote: Compressing objects: 100% (6/6), done.remote: Total 9 (delta 3), reused 9 (delta 3), pack-reused 0Unpacking objects: 100% (9/9), 54.81 KiB | 597.00 KiB/s, done.From github.com:mc0de/rds-ec2-project 901ecc4..50ae9b6 main -> origin/mainUpdating 901ecc4..50ae9b6Fast-forward .gitignore | 1 - public/build/assets/app.73cd3409.css | 1 + public/build/assets/app.d426e523.js | 32 ++++++++++++++++++++++++++++++++ public/build/manifest.json | 12 ++++++++++++ 4 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 public/build/assets/app.73cd3409.css create mode 100644 public/build/assets/app.d426e523.js create mode 100644 public/build/manifest.json
- Now your project is LIVE, let's share it with some friends! But wait, the URL
http://<YOUR-SERVER-IP-ADDRESS>
is very hard to memorize and not convenient at all. You may purchase a domain name on one of the providers and add DNS A type record with the value of your public IP address.
On your domain provider's panel entry should be similar to this:
A aws-course.laraveldaily.com 18.195.117.231 # your actual server ip address
or if you don't use a subdomain it even may look similar to that, using @
instead of name:
A @ 18.195.117.231
Configuration used in this tutorial doesn't need any additional changes on the server to support the domain name, all HTTP requests will resolve to the default site.
Congratulations, you've completed this "Deploy Laravel to AWS" course!
Our demo project used in this tutorial with all exact configurations can be accessed at aws-course.laraveldaily.com
I have a Dockerfile and a Docker Image to help me deploying laravel apps running inside a docker container
Dockerfile: https://github.com/antonioanerao/dockerfile-laravel
Docker Image: https://hub.docker.com/r/antonioanerao/laravel
It's quite simple to run
May I ask, Antonio, how do you deal with the database? Do you run a separate container with the database?
@wolfatadfilm if I'm using for some reason a sqlite database I don't mind to use the sqlite file in the same container, however, if I'm using a MySQL/SQL Server database I prefer to keep in a separatede container. In some cases I even use a differente machine for that (in the same network).
I have a few projects here I have a machine for webservers and another one for databases. I keep one docker-compose for each database so I can have more control about everything.
I have an error when Itry to open my URL I got 404 Not Found nginx/1.18.0 (Ubuntu)
This tutorial ended too quickly. Currently, I need to google how to set up a custom domain name, so my project is accessible via mydomain.com, how to set up supervisord so queue works and mailgun free tier, so my mails are being sent out. For the current price of monthly subscription, I expected more.
Thanks for the feedback.
Generally, if you're not happy with the content of monthly subscription, feel free to unsubscribe, we're trying to cover a lot of topics but there's only so much time in the day and so many topics we can publish. We're doing our best and looking at what to improve depending on the feedback.
I think this tutorial was just the right size, it is called "Deploy Laravel Project to AWS EC2: Step-By-Step" which I believe it does that quickly and sufficiently. Setting up a domain is a very individual thing people prefer one company to another and people would complain that the steps are different from their preferred registrar etc
Queues.... did you really ask about queues.... that is a tutorial and a half in itself!
My only criticism would be the RDS - whilst the best way to go a lot of people might be put off by the higher RDS costs for a small database especially for a project. I ended up just installing MySQL 8.x manually and configuring my Laravel app with this instead, which I believe is sufficient for most but that is just my 2 cents :) Great tutorial Povilas!
What about frontend pressets like React js?
I have installed nvm and using nvm I installed the node version on the project directory. Using
run npm prod
I build the js files. Is the the correct way?hi, we did not test front-end presets. if it works for you, what you did is correct way.
I got a 404 error... no clue what's going on. I have a webpack.mix.js file because I'm using React.js in my project so maybe that's why? But I'm not sure. my /etc/nginx/sites-enabled/default is identical to what's posted above (except for demo_project) so I can rule that out.
What could be the issue?
it is impossible to tell what the reason of 404 is without seeing more logs or debug info.
Fixed it actually, I forgot to restart nginx and php-fpm
This is good article but domain side is remaining like manage domains DNS, SSL etc, what If we need another sub domain in this same instance ;)
A topic for the future, thanks. But I think it's way outside of topic of Laravel, it's more about DevOps.
What about a setup with two or more application servers and a load balancer? How to set that up, and how to make updates to it in the future?
We were thinking about a tutorial for load balancer, but it seems to be more of a DevOps topic than developer/Laravel. So, for now, we don't plan to write about it. I recommend you watch these videos on ServersForHackers: https://serversforhackers.com/s/load-balancing-with-nginx
I understand your point, it's OK. Thanks for the reply and the useful tutorial.
Good article, helps someone go from building and customising a web server all the way through connection and deployment with the source code repo.
Well done team!
i got error pemission denied when save file nginx/default.conf. Help me thanks
and i got error 404 not found nginx when root is hom/web/demoproject
chmod: changing permissions of '/home/web': Operation not permitted in step 5 web@ip-172-31-44-101:~$ chmod 755 /home/web
Hi, Thank you for this nice tutorial. Everything worked like a charm and I deployed laravel app to aws for the first time. Thank you so much.
You helped me conquer the myth of AWS. Thanks.
Finally got an app deployed to AWS - thank you this is a perfect tutorial for EC2!
403 Forbidden nginx/1.24.0 (Ubuntu)
i have complete everything but yet i got this error message. i have no idea why nginx stopped running
can you help me with this issue ?
403 Forbidden nginx/1.24.0 (Ubuntu)
i have complete everything but yet i got this error message. i have no idea why nginx stopped running