How I deploy Laravel apps

Published by at 29th January 2018 10:00 pm

A while back I provided details of the web server setup I used for Django applications. Nowadays I tend to use Laravel most of the time, so I thought I'd share an example of the sort of setup I use to deploy that.

Server OS

As before I generally prefer Debian Stable where possible. If that's not possible for any reason then the current Ubuntu LTS is an acceptable substitute.

Web server

My usual web server these days is Nginx with PHP 7 or better via FPM. I generally use HTTP2 where possible, with SSL via Let's Encrypt.

Here's my typical Nginx config:

1fastcgi_cache_path /etc/nginx/cache levels=1:2 keys_zone=my-app:100m inactive=60m;
2fastcgi_cache_key "$scheme$request_method$host$request_uri";
3add_header Content-Security-Policy "default-src 'self'; script-src 'self'; img-src 'self'; style-src 'self' ; font-src 'self'; frame-src 'none'; object-src 'none'";
4server_tokens off;
6server {
7 listen 80;
8 listen [::]:80;
9 server_name my-app.domain;
10 return 301 https://$server_name$request_uri;
13server {
14 listen 443 ssl http2;
15 listen [::]:443 ssl http2;
16 include snippets/ssl-my-app.domain.conf;
17 include snippets/ssl-params.conf;
18 client_max_body_size 50M;
19 fastcgi_param HTTP_PROXY "";
21 access_log /var/log/nginx/access.log;
22 error_log /var/log/nginx/error.log;
24 root /var/www/my-app.domain/current/public;
25 index index.php index.html index.htm;
27 server_name my-app.domain;
29 location / {
30 try_files $uri $uri/ /index.php?$query_string;
31 }
33 location ~ \.php$ {
34 try_files $uri /index.php =404;
35 fastcgi_split_path_info ^(.+\.php)(/.+)$;
36 fastcgi_pass unix:/var/run/php/php7.0-fpm-my-app.sock;
37 fastcgi_index index.php;
38 fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
39 include fastcgi_params;
40 fastcgi_cache my-app;
41 fastcgi_cache_valid 200 60m;
42 }
44 location ~ /.well-known {
45 allow all;
46 }
48 location ~* \.(?:manifest|appcache|html?|xml|json)$ {
49 expires -1;
50 gzip on;
51 gzip_vary on;
52 gzip_types application/json text/xml application/xml;
53 }
55 location ~* \.(?:rss|atom)$ {
56 expires 1h;
57 add_header Cache-Control "public";
58 gzip on;
59 gzip_vary on;
60 gzip_types application/xml+rss;
61 }
63 location ~* \.(?:jpg|jpeg|gif|png|ico|cur|gz|svg|svgz|mp4|ogg|ogv|webm|htc)$ {
64 expires 1M;
65 access_log off;
66 add_header Cache-Control "public";
67 }
69 location ~* \.(?:css|js)$ {
70 expires 1y;
71 access_log off;
72 add_header Cache-Control "public";
73 gzip on;
74 gzip_vary on;
75 gzip_types text/css application/javascript text/javascript;
76 }

The times for FastCGI caching tend to vary in practice - sometimes it's not appropriate to use it all, while for others it can be cached for some time.

It's generally fairly safe to cache CSS and JS for a long time with a Laravel app if you're using Mix to version those assets, so I feel comfortable caching them for a year. Images are a bit dicier, but still don't change often so a month seems good enough.

I'll typically give each application its own pool, which means copying the file at /etc/php/7.0/fpm/pool.d/www.conf to another file in the same directory, amending the pool name and path to set a new location for the socket, and then restarting Nginx and PHP-FPM. Here are the fields that should be changed:

1; Start a new pool named 'www'.
2; the variable $pool can be used in any directive and will be replaced by the
3; pool name ('www' here)
6listen = /var/run/php/php7.0-fpm-my-app.sock


I'm a fan of PostgreSQL - it's stricter than MySQL/MariaDB, and has some very useful additional field types, so where possible I prefer to use it over MySQL or MariaDB.

Cache and session backend

Redis is my usual choice here - I make heavy use of cache tags so I need a backend for the cache that supports them, and Memcached doesn't seem to have as much inertia as Redis these days. Neither needs much in the way of configuration, but you can get a slight speed boost by using phpiredis.


I sometimes use Redis for this too, but it can be problematic if you're using Redis as the queue and broadcast backend, so these days I'm more likely to use Beanstalk and keep Redis for other stuff. I use Supervisor for running the queue worker, and this is an example of the sort of configuration I would use:

3command=php /var/www/artisan queue:work --sleep=3 --tries=3

This is fairly standard for Laravel applications.


I often make use of the Laravel scheduled tasks system. Here's the typical cron job that would be used for that:

* * * * * php /var/www/artisan schedule:run >> /dev/null 2>&1

Again, this is standard for Laravel applications. It runs the scheduler every minute, and the scheduler then determines if it needs to do something.


To set all this up, I'll generally use Ansible. In addition to this, I'll generally also set up fail2ban to block various attacks via both HTTP and SSH.