How to configure Virtualmin's Nginx server for a Laravel application

Deployment

Introduction

 This article describes a quick steps and a longer explanation of how to get a Laravel application working with Virtualmin and NGINX. Of course most sane people would use Forge to host Laravel, but if you already have access to Virtualmin servers this article could be of some use. In theory the information here should also apply to the Yii PHP Framework. To get started, create a Virtualmin server as per usual. Then publish your app (git pull?) somewhere below /home/username/, e.g. /home/username/app-name/ Note, we have not specified the default of public_html. I typically name this directory the "shorter" version of my app name because naming it too generic means on a multi-site servers one can easily get lost when navigating. Now follow the instructions below. In summary: 

  • Steps 1 to 3 involves changing all references of public_html to app-name/public
  • Step 4 is an addition to allow URL rewriting.
  • Step 5 is simply to restart NGINX.

The short version skips a lot of the default stuff that the Laravel website mentions as per the reference and is like a minimal way of getting things going. 

Quick Steps

vi /etc/nginx/sites-available/sitename.com.conf
lang-bash

 1. Change 

root /home/sitename/public_html;
lang-bash

 to 

root /home/sitename/app_name/public;
lang-bash

 2. Change 

fastcgi_param SCRIPT_FILENAME /home/sitename/public_html$fastcgi_script_name;
lang-bash

 to 

fastcgi_param SCRIPT_FILENAME /home/sitename/app_name/public$fastcgi_script_name;
lang-bash

 3. Change 

fastcgi_param DOCUMENT_ROOT /home/sitename/public_html;
lang-bash

 to 

fastcgi_param DOCUMENT_ROOT /home/sitename/app_name/public;
lang-bash

 4. Add below listen (but could be anywhere). This is the friendly URL rewriter. 

location / { try_files $uri $uri/ /index.php?$query_string; }
lang-bash

 5. Restart NGINX 

service nginx restart
lang-bash

Longer Explanation

 This longer explanation has more detail and references the Laravel website guidance for NGINX. To summarize, the key changes that you will make is that the server root, and fastcgi_param SCRIPT_FILENAME and fastcgi_param DOCUMENT_ROOT locations must be updated to specify your application location. Additionally you can add all the additional directives as specified on the Laravel website here which handles headers, 404s, and a couple of other things. Our complete example below shows you a full configuration file with additional comments embedded to show what we have done. In this example, let's say our domain is called mydomain.com and our application is called hello-world. We've deployed hello-world in the following directory on the Virtualmin server: 

/home/mydomain.com/hello-world
lang-bash

 For this example, the following configuration file listed below should work. It's a blend between the information on the Laravel documentation website, and the pre-existing configuration file created by Virtualmin. See the bold text for highlights and important directory locations. Example configuration file based on mydomain.com and hello-world:

server {
   # Start Laravel Additions
   add_header X-Frame-Options "SAMEORIGIN";
   add_header X-XSS-Protection "1; mode=block";
   add_header X-Content-Type-Options "nosniff";
   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_index index.php;
      fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
      include fastcgi_params;
      try_files $uri =404;
      fastcgi_pass unix:/var/php-nginx/15574097604771.sock/socket;
   }
   location ~ /\.(?!well-known).* {
      deny all;
   }
   # End Laravel Additions
   # Now:
   # 1. Comment out the Virtualmin location block
   # 2. Copy the NGINX unique socket ID line in the commented block below try_files $uri = 404;
   # 3. There are three references to public_html, change all three to /home/yourdomain/yourapp/public
   # 4. Replace ip_address with the IP address in the existing Virtualmin configuration

   server_name mydomain.com www.mydomain.com
   listen ip_address;
   root /home/mydomain.com/hello-world/public;
   index index.html index.htm index.php;
   access_log /var/log/virtualmin/mydomain.com_access_log;
   error_log /var/log/virtualmin/mydomain.com_error_log;
   fastcgi_param GATEWAY_INTERFACE CGI/1.1;
   fastcgi_param SERVER_SOFTWARE nginx;
   fastcgi_param QUERY_STRING $query_string;
   fastcgi_param REQUEST_METHOD $request_method;
   fastcgi_param CONTENT_TYPE $content_type;
   fastcgi_param CONTENT_LENGTH $content_length;
   fastcgi_param SCRIPT_FILENAME /home/mydomain.com/hello-world/public$fastcgi_script_name;
   fastcgi_param SCRIPT_NAME $fastcgi_script_name;
   fastcgi_param REQUEST_URI $request_uri;
   fastcgi_param DOCUMENT_URI $document_uri;
   fastcgi_param DOCUMENT_ROOT /home/mydomain.com/hello-world/public;
   fastcgi_param SERVER_PROTOCOL $server_protocol;
   fastcgi_param REMOTE_ADDR $remote_addr;
   fastcgi_param REMOTE_PORT $remote_port;
   fastcgi_param SERVER_ADDR $server_addr;
   fastcgi_param SERVER_PORT $server_port;
   fastcgi_param SERVER_NAME $server_name;
   fastcgi_param HTTPS $https;
   # This section commented out intentionally because it's been moved up to the Laravel specific section
   #location ~ \.php$ {
   # try_files $uri =404;
   # fastcgi_pass unix:/var/php-nginx/15574097604771.sock/socket;
   #}
   listen ip_address:443 ssl;
   ssl_certificate /home/mydomain.com/ssl.combined;
   ssl_certificate_key /home/mydomain.com/ssl.key;
}
lang-bash

Redirect all HTTP requests to HTTPS

 As HTTP pages should really be served as HTTPS, add another server section below or above this one. The contents should be:

server {
server_name mydomain.com www.mydomain.com
listen ip_address:80;
rewrite ^/(.*) https://mydomain.com/$1 permanent;
}
lang-bash

Getting Let's Encrypt SSL Renewals to work automatically

 The one problem you will find is that Let's Encrypt doesn't renew automatically anymore. The reason is Virtualmin expects certain well known items to be in public_html, but of course, we're not serving out of there anymore. Add this server location block to get around the problem: 

location ^~ /.well-known/acme-challenge/ {
default_type "text/plain";
root /home/air2/public_html;
}
lang-bash

References