Host Your Own Website Using Grav Nginx PHP and a Rock64

18-10-2021 - 5 minutes, 40 seconds -

You can host a fast, quick loading full website on a tiny ARM single board computer with only 1GB of RAM, all for under $20. Crazy? Well that's the exact setup you're reading this on right now. Let me show you how.

Currently the Pine64's RK3328 based SBC the Rock64 is for sale on Amazon for $15.99. You get a case, power supply, 8GB mSD card, and even a 16x2 Serial LCD display with I2C library chip attached. It's an amazing deal. But even if you miss this deal, there are plenty of similar boards you can run this setup on. This writeup will specifically cover this board and I've upgraded the mSD card to a larger size to allow for image uploads. Also I am running an Nginx reverse proxy on another machine to handle my domain trafficing and SSL.

We'll be using Grav as our web framework. Grav is flat file CMS that just uses markdown to create pages and no database is required so it's incredibly fast and perfectly suited to smaller machines with limited resources. Also our webserver will be Nginx which is well suited to the Rock64 or other SBCs.

Start by downloading Armbian and writing to the mSD card using your preferred method. Armbian has extensive documentation so I won't cover the setup details. Once the Rock64 is powered on with the mSD card inserted, SSH to the machine and go through the standard setup procedures. Make sure to update the repos and upgrade. You'll probably be asked to reboot.

Next install Nginx:

$ sudo apt install nginx

You'll need PHP and certain extensions as well:

$ sudo apt install php7.4 php7.4-curl php7.4-gd php7.4-fpm php7.4-cli php7.4-opcache php7.4-mbstring php7.4-xml php7.4-zip

Test your PHP installation:

$ php -v
PHP 7.4.3 (cli) (built: Oct  6 2020 15:47:56) ( NTS )
Copyright (c) The PHP Group
Zend Engine v3.4.0, Copyright (c) Zend Technologies
     with Zend OPcache v7.4.3, Copyright (c), by Zend Technologies

It is recommended to use an updated mime.types file with nginx. You can find it here. Append your original file and copy the mime.types from the link into a new mime.types file:

$ sudo mv /etc/nginx/mime.types /etc/nginx/mime.types.bak
$ sudo nano /etc/nginx/mime.types

Use the recommended nginx configuration from the official Grav documentation:

$ sudo mv /etc/nginx/nginx.conf /etc/nginx/nginx.conf.bak
$ sudo nano /etc/nginx/nginx.conf

Copy and paste the following into the new nginx.conf

user www-data;
worker_processes auto;
worker_rlimit_nofile 8192; # should be bigger than worker_connections
pid /run/nginx.pid;

events {
    use epoll;
    worker_connections 8000;
    multi_accept on;
}

http {
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;

    keepalive_timeout 30; # longer values are better for each ssl client, but take up a worker connection longer
    types_hash_max_size 2048;
    server_tokens off;

    # maximum file upload size
    # update 'upload_max_filesize' & 'post_max_size' in /etc/php/fpm/php.ini accordingly
    client_max_body_size 32m;
    # client_body_timeout 60s; # increase for very long file uploads

    # set default index file (can be overwritten for each site individually)
    index index.html;

    # load MIME types
    include mime.types; # get this file from https://github.com/h5bp/server-configs-nginx
    default_type application/octet-stream; # set default MIME type

    # logging
    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log;

    # turn on gzip compression
    gzip on;
    gzip_disable "msie6";
    gzip_vary on;
    gzip_proxied any;
    gzip_comp_level 5;
    gzip_buffers 16 8k;
    gzip_http_version 1.1;
    gzip_min_length 256;
    gzip_types
        application/atom+xml
        application/javascript
        application/json
        application/ld+json
        application/manifest+json
        application/rss+xml
        application/vnd.geo+json
        application/vnd.ms-fontobject
        application/x-font-ttf
        application/x-web-app-manifest+json
        application/xhtml+xml
        application/xml
        font/opentype
        image/bmp
        image/svg+xml
        image/x-icon
        text/cache-manifest
        text/css
        text/plain
        text/vcard
        text/vnd.rim.location.xloc
        text/vtt
        text/x-component
        text/x-cross-domain-policy;

    # disable content type sniffing for more security
    add_header "X-Content-Type-Options" "nosniff";

    # force the latest IE version
    add_header "X-UA-Compatible" "IE=Edge";

    # enable anti-cross-site scripting filter built into IE 8+
    add_header "X-XSS-Protection" "1; mode=block";

    # include virtual host configs
    include sites-enabled/*;
}

You will need a new conf file for your Grav site:

sudo nano /etc/nginx/sites-available/grav_site.conf

Copy and paste the following into your grav-site.conf file:

server {
    #listen 80;
    index index.html index.php;

    ## Begin - Server Info
    root /var/www/html/grav;
    server_name localhost;
    ## End - Server Info

    ## Begin - Index
    # for subfolders, simply adjust:
    # `location /subfolder {`
    # and the rewrite to use `/subfolder/index.php`
    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }
    ## End - Index

    ## Begin - Security
    # deny all direct access for these folders
    location ~* /(\.git|cache|bin|logs|backup|tests)/.*$ { return 403; }
    # deny running scripts inside core system folders
    location ~* /(system|vendor)/.*\.(txt|xml|md|html|yaml|yml|php|pl|py|cgi|twig|sh|bat)>
    # deny running scripts inside user folder
    location ~* /user/.*\.(txt|md|yaml|yml|php|pl|py|cgi|twig|sh|bat)$ { return 403; }
    # deny access to specific files in the root folder
    location ~ /(LICENSE\.txt|composer\.lock|composer\.json|nginx\.conf|web\.config|htacc>
    ## End - Security

    ## Begin - PHP
    location ~ \.php$ {
        # Choose either a socket or TCP/IP address
        fastcgi_pass unix:/var/run/php/php7.4-fpm.sock;
        # fastcgi_pass unix:/var/run/php5-fpm.sock; #legacy
        # fastcgi_pass 127.0.0.1:9000;

        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root/$fastcgi_script_name;
    }
    ## End - PHP
}

Move nginx's default site out so that it doesn't load instead of your Grav site and link sites-available to sites-enabled:

$ cd /etc/nginx/sites-enabled/
$ sudo mv default ~/default-sites-enabled
$ sudo ln -s ../sites-available/grav_site.conf

Restart nginx and php-fpm:

$ sudo service nginx restart
$ sudo service php7.4-fpm restart

Now we need to get Grav itself. The easiest way is just to download the zip from Grav:

$ wget 'https://getgrav.org/download/core/grav-admin/latest'
$ unzip grav-admin-v1.6.28.zip
$ sudo mv grav-admin /var/www/html/grav

Give the correct webserver permissions to Grav to avoid permissions errors:

$ sudo chown -R www-data:www-data /var/www/html/grav

If you go to you webserver's local address you should be greeted with the Grav default page, /admin will enable you to create an admin account through the admin panel.

I am running my Grav installation behind an nginx proxy running on a separate machine. Therefore I need to create a conf file for the Grav server and enable SSL. SSH into the proxy server and create the file:

$ sudo nano /etc/nginx/sites-enabled/tech.minnix.dev.conf`

Paste the following into your conf:

    server {
    server_name your-grav-site.com www.your-grave-site.com;
    set $upstream "your Grav server's local IP";

    location / {
    proxy_pass_header Authorization;
    proxy_pass http://$upstream;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_http_version 1.1;
    proxy_set_header Connection “”;
    proxy_buffering off;
    client_max_body_size 0;
    proxy_read_timeout 36000s;
    proxy_redirect off;
    }

    listen 80;

    }

Save the file and test your conf:

$ sudo nginx -t

If everything's ok, should return:

nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

I like to use both the address and the www.address in my conf file just in case someone types in either one. So in my conf file I have minnix.tech.dev and www.minnix.tech.dev. Proceed to generate your SSL certs using certbot for https access.

$ sudo apt update && install certbot
$ sudo certbot --nginx

You will be asked which domains you wish to generate a certificate for. Choose the domains and certbot will then automatically generate your cert and fill in your nginx site conf with the required info to enable HTTPS. Go to your new domain's address within your browser and you should see your site with HTTPS enabled.