The SLAP stack is the last server stack you'll ever need
I just like saying that. Clearly you should use whatever you want. “What is the SLAP stack?” you ask. Well, I just invented the name on Mastodon a few days ago. I don’t even want to do a web search to see if it’s already taken because that will take the wind out of my sails and I won’t finish this blog post.
But first, there’s something you should know about me. I have this aphorism I live by: everyone is weird and everything is complicated. The former is mostly meant as a compliment. But today, I’m going to do what I can to mitigate the latter. And you know what’s complicated? Web apps. There are so many tech stacks you can choose for websites and web apps these days. They all have these dumb acronyms—LAMP, LEMP, MEAN, MERN, JAM.[1]
Let’s take the LAMP stack, for example. It stands for Linux, Apache, MySQL, and PHP. This has to be the most popular server stack on the internet thanks to the popularity of WordPress, the venerable blog and website content management system that powers a whopping 40-something percent of all the websites on the internet.[2]
WordPress uses PHP to reach into the MySQL database and construct whatever page is being requested. There’s quite a lot that goes on when WordPress does that, and it can be slow. It’s also pretty old (if actively maintained) software and isn’t always the funnest thing to develop sites with. I’m not saying it’s bad, just pointing out a few reasons why people might choose a different piece of software—or a different server stack altogether.
The rise of JavaScript on the server (i.e., Node), and frontend frameworks like React led to other stacks like MERN (MongoDB, Express, React, and Node). A lot of people are using static site generators which allows them to remove server-side complexities (although it potentially comes at a cost of introducing more complicated client-side JavaScript).
I do a lot of low to medium-size websites and apps. For these types of apps I’ve really come around to enjoying the SLAP stack—SQLite, Linux, Apache, and PHP. It can do it all, and doesn’t require you to run a database server. And I don’t mean to imply it’s only good for small projects. I think this stack can scale quite well considering its simplicity. Let’s talk about it.
The SLAP stack: SQLite, Linux, Apache, and PHP
Alright, first of all, if you thought I was going to go into a nerdy, analytical deep dive on the performance of SQLite and/or this stack let me go ahead and disappoint you right now. I am way too lazy to look into all that and get specific numbers. I’m kind of going by what I’ve read and what I’ve learned so far in the very cool course, High Performance SQLite.
Let me take each technology in turn and discuss.
SQLite
Here is a summary of my current understanding and intuition about SQLite.
- The database is one file[3] and doesn’t need a database server
- Easy to backup because it’s one file and has a backup command
- Really good at reading data quickly
- Not super great if you have a ton of concurrent writers (1,000+ writes per second maybe?)
- Not super great if you have over a terabyte of data
- Can (and does) easily power a website
- For a website that will be doing a decent amount of writing, you should use the write-ahead logging (or WAL) option
I’ve only started scratching the surface of this technology so there is a ton I don’t know about it.
Linux
Really nothing to talk about here as many, many web servers are running Linux. But if you’re curious, I am running Ubuntu 26 LTS for my app in question (which I will discuss shortly).
Apache
I’m using Apache because it’s simple and boring and because the apps I build typically use .htaccess files to disallow access to certain files and directories.
PHP
I only really know two programming languages well enough to build web apps with them—PHP and JavaScript. I’m not proud of this, but it is what it is and I’m busy. My side projects need to be built with what I know. Sometimes I go vanilla PHP, other times I use my beloved Fat Free Framework.
SLAP the app together
All of these technologies have been around for a long time. They’re mature, well understood, and there’s plenty of information on the web about them. For me, as someone who only has nights and weekends available to mess around building apps, this is perfect. I do enjoy learning new languages. And I enjoy the act of programming, even if it’s an exercise or puzzle. But I live for the holistic thing itself—the website, the application, whatever it is. I love combining all the disciplines—backend code, frontend code, UI/UX, writing, art, soul—and delivering a useful app that people like. This is my passion.
Next up, I want to show you how you can set up the SLAP stack on a VPS (virtual private server). This is a step-up from shared hosting because it means you’re managing most aspects of the server yourself. This fact scared me for a very long time. But recently my hand was forced (my shared host’s filesystem wasn’t a good fit[4]), and I decided it was time to buckle up and learn this ancient skill. I’m sharing what I’ve learned here but with the disclaimer that I am a beginner at server management. You should definitely check up on anything I tell you to do here.
The stuff I’m going to show you should work for most SLAP stack projects, but I want to tell you briefly about my specific project, Minimal Character Sheet. I’ve written about my freeform digital character sheet previously but, to summarize, this is a web app that facilitates Dungeons & Dragons character sheets in a format that is very similar to form-fillable PDFs. But unlike PDFs, there is plenty of room to write. I also throw in some extra goodies like dice rolling and doing some light number-crunching (for ability and skill modifiers).
It runs on the SLAP stack and uses Fat Free Framework. F3 is another blog post altogether, but it’s the one PHP framework I’ve found that is as simple and noninvasive as I need.
The app has moderately low traffic at the moment with around 250 monthly active users. Its needs of the server are light—it’s mostly shipping JSON between the frontend and backend. It does need to send emails related to authentication, but does that with the email sending service, Postmark.
One regret I have about this project is that, much like WordPress, all of its files are in the public HTML root. This means I have to take care to block access to certain parts of the app (like the database). I’d like to refactor the structure of the app at some point, but I have other, more pressing, things on the to-do list.
How to set up the SLAP stack on a VPS
I made a ton of notes while I was figuring out this process and I was doing a lot manually at first. But I narrowed things down to a few reproducible steps using scripts. That’s what I’ll share here.
First up, you’ll need a VPS provider. The field is huge and pricing is all over the place. It seems everyone loves Hetzner, but they had a recent price increase and seem to be rationing the cheaper VPS options at the time of this writing. DigitalOcean is another big name here. They call their VPS products, Droplets. This would probably be my choice, except for the fact that this self-hosting project is experimental for me and I want the cheapest reliable-ish VPS I can find. So I grabbed a $5/month no-commitment VPS from IONOS. I’m not recommending the service per se, but I was able to get a VPS up and running with decent specs at a low cost. So far so good.
What we’re installing
After a bit of research I determined that these are the packages I need to install on my VPS for the simple app that I’m running.
apache2: the Apache web serversqlite3: the SQLite3 CLIphp: the latest version of phplibapache2-mod-php: this is what lets Apache execute PHP filesphp-sqlite3: the SQLite3 extension for PHPphp-opcache: automatically caches compiled PHP (not needed for PHP 8.5 turns out, but install it if you need it)php-mbstring: I don’t entirely understand this one but I think it is essentially support for UTF-8php-curl: enable a PHP app to make network requestsphp-xml: DOM-parsing capabilitiesphp-zip: I have a backup script that will need to zip up filesmicro: this is a text editor I prefer to use because it has some mouse support, which is helpful for me
Cloud config
After messing around with different options, I found that the quickest way to get a VPS provisioned with the stuff you want on it is to use a cloud config. This is a yaml file which lets you specify a configuration for your VPS much like you would do with a Dockerfile. I think most, if not all, VPS providers have some way for you to supply this configuration file during the VPS setup process.
Using this cloud config will let us set up a user with our ssh key(s) and disable the root user as well as password authentication. All of this is part of the basic user setup you would do for a new VPS. In addition, we can specify the packages to install, set some permissions, and get our firewall up and running with a basic configuration.
1#cloud-config 2 3package_update: true 4 5users: 6 - name: YOUR_USER 7 groups: 8 - sudo 9 shell: /bin/bash10 sudo: ALL=(ALL) NOPASSWD:ALL11 lock_passwd: true12 ssh_authorized_keys:13 - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA... keys go here14 15disable_root: true16ssh_pwauth: false17 18packages:19 - ufw20 - micro21 - apache222 - php23 - libapache2-mod-php24 - sqlite325 - php-sqlite326 - php-mbstring27 - php-curl28 - php-xml29 - php-zip30 - php-opcache # if < php v8.531 32write_files:33 - path: /etc/ssh/sshd_config.d/00-hardening.conf34 owner: root:root35 permissions: "0644"36 content: |37 PasswordAuthentication no38 KbdInteractiveAuthentication no39 PubkeyAuthentication yes40 PermitRootLogin no41 42runcmd:43 - ufw allow OpenSSH44 - ufw allow "Apache Full"45 - ufw --force enable46 - sshd -t47 - systemctl reload ssh48 - systemctl enable --now apache2
Configuring Apache and getting an SSL cert
Once my VPS is running and I can SSH into it, I run the following script. It’s doing several things for me. The most important being configuring Apache and getting an SSL certificate. The script assumes that you have a domain pointing at the IP address of your VPS.
I tried to keep everything simple and understandable, but I was getting an error related to the JIT compiler and I added a magic line of configuration that I don’t really understand (if you are reading this and you know what that is, and a better way for me to fix it, please let me know).
The script expects a few variables, so you should run it like this:
1DOMAIN="example.com" SSH_USER="USERNAME" sh vps-setup.sh
1#!/bin/bash 2 3# Stop the script if a command fails. 4set -e 5 6# This is where the website will live on the server. 7WEB_ROOT="/var/www/$DOMAIN/public" 8 9# This server does not allow PCRE to allocate the executable memory used by10# its optional JIT compiler. Regular expressions still work without it.11echo "pcre.jit=0" | sudo tee /etc/php/8.5/apache2/conf.d/99-pcre-jit.ini12 13# Create the website directory.14sudo mkdir -p "$WEB_ROOT"15 16# Create the Apache configuration file.17sudo tee "/etc/apache2/sites-available/$DOMAIN.conf" > /dev/null <<EOF18<VirtualHost *:80>19 ServerName $DOMAIN20 21 DocumentRoot $WEB_ROOT22 23 <Directory $WEB_ROOT>24 Options -Indexes +FollowSymLinks25 AllowOverride All26 Require all granted27 </Directory>28 29 ErrorLog \${APACHE_LOG_DIR}/$DOMAIN-error.log30 CustomLog \${APACHE_LOG_DIR}/$DOMAIN-access.log combined31</VirtualHost>32EOF33 34# Enable URL rewriting and the new website.35sudo a2enmod rewrite36sudo a2ensite "$DOMAIN.conf"37 38# Check the Apache configuration and reload Apache.39sudo apache2ctl configtest40sudo systemctl reload apache241 42# Install Certbot and request an SSL certificate.43sudo apt install -y certbot python3-certbot-apache44sudo certbot --apache \45 --non-interactive \46 --agree-tos \47 --no-eff-email \48 --email "$CERTBOT_EMAIL" \49 -d "$DOMAIN"50 51# Make the data directory.52sudo mkdir -p "$WEB_ROOT/data"53 54# Create the temporary directory.55sudo mkdir -p "$WEB_ROOT/tmp"56 57# Set the normal permissions for the deployed files.58sudo chown -R "$SSH_USER:www-data" "$WEB_ROOT"59sudo find "$WEB_ROOT" -type d -exec chmod 750 {} \;60sudo find "$WEB_ROOT" -type f -exec chmod 640 {} \;61 62# Make data writeable by php63sudo chmod 770 "$WEB_ROOT/data"64 65# Make tmp directory writeable66sudo find "$WEB_ROOT/tmp" -type d -exec chmod 770 {} \;67sudo find "$WEB_ROOT/tmp" -type f -exec chmod 660 {} \;68 69# Deployment is handled outside this script.70echo71echo "Deploy the website to $WEB_ROOT now. Be sure to upload the environment file and the database (if it exists)."
The script is a bit specific to my setup. It assumes that everything is going into a public directory as the web root. As my app has a data directory inside of the web root (again, this is not a good idea, but my application does block access to that directory with a .htaccess file), the script creates the folder and sets the permissions for it. My PHP framework also makes use of a tmp folder, which I create and make writable here.
The Apache configuration is a very basic one that allows .htaccess files to work and disables automatic file listings.
The script also installs certbot which will grab an SSL certificate from Let’s Encrypt and create a task to renew it automatically.
A little message at the end is just a reminder of what to do next.
Deploying a website to your VPS
At this point, we need to get our website files into our web root directory. The how doesn’t matter so much here—use whatever’s convenient for you. The absolute quickest way to get started would probably be to use rysnc, a command line program for transferring files. Don’t underestimate the power of rsync! Your project may be suitable for a one-line deployment script.
1rsync -avz --delete mysite/ user@example.com:/var/www/example.com/public/
If you’re like me and you like to use a nice GUI instead of the command line, you could use an SFTP client and manually move your website over.
I like to use a service called DeployHQ for this purpose. It lets you set up a server connection and choose a git repo. You can set up a build pipeline if needed to run any build scripts. When you’re ready to deploy, you can choose which commit you would like to deploy, and DeployHQ will build everything and transfer the necessary files. It has some niceties like only transferring the files that have changed since the last deployment as well as a quick rollback feature if you make a mistake. If you are like me and you have a bunch of small projects, it can get a little pricey.[5]
GitHub, GitLab, Bitbucket—they all have fancy ways to deploy stuff. I’m gonna be honest I haven’t really tried them and don’t know much about them. Point is you have a lot of options. An automatic deployment process can be really nice but isn’t necessary.
Write-ahead logging mode
I mentioned earlier that if your website is doing a lot of writing then you want to use write-ahead logging (or WAL) for your SQLite database. According to the SQLite website, this mode is usually faster. It does not work with a networked file system, however, which is the reason I began this whole journey in the first place.
All processes using a database must be on the same host computer; WAL does not work over a network filesystem. This is because WAL requires all processes to share a small amount of memory and processes on separate host machines obviously cannot share memory with each other.
— Write-Ahead Logging, SQLite.org
You might do this as some sort of migration script or other or you can do it manually. If you want to use this option, you can run a command like this:
1sudo sqlite3 "/path/to/db.sqlite3" "PRAGMA journal_mode=WAL;"
Setting permissions on your website files
Once the website files are in place, we need to make sure they have the correct permissions. I have a set-permissions.sh script that ensures that all files start with base permissions, then elevates permissions for whatever files needs to be writeable. The script takes the same variables as the setup script.
1DOMAIN="example.com" SSH_USER="USERNAME" sh vps-setup.sh
Again, the script is a bit specific to my particular app. Feel free to change the paths to fit your own application.
1#!/bin/bash 2 3# Stop the script if a command fails. 4set -e 5 6# This is where the website will live on the server. 7WEB_ROOT="/var/www/$DOMAIN/public" 8 9# Create the temporary directory.10sudo mkdir -p "$WEB_ROOT/tmp"11 12# Set the normal permissions for the deployed files.13sudo chown -R "$SSH_USER:www-data" "$WEB_ROOT"14sudo find "$WEB_ROOT" -type d -exec chmod 750 {} \;15sudo find "$WEB_ROOT" -type f -exec chmod 640 {} \;16 17# Make the data directory writable by PHP.18sudo chmod 770 "$WEB_ROOT/data"19 20# If the database file exists, make it writable by PHP as well.21if [ -f "$WEB_ROOT/data/db.sqlite3" ]; then22 sudo chmod 660 "$WEB_ROOT/data/db.sqlite3"23fi24 25# If the database file exists, make it writable by PHP as well.26if [ -f "$WEB_ROOT/data/db.sqlite3-shm" ]; then27 sudo chmod 660 "$WEB_ROOT/data/db.sqlite3-shm"28fi29 30# If the database file exists, make it writable by PHP as well.31if [ -f "$WEB_ROOT/data/db.sqlite3-wal" ]; then32 sudo chmod 660 "$WEB_ROOT/data/db.sqlite3-wal"33fi34 35# make it writable by PHP36sudo find "$WEB_ROOT/tmp" -type d -exec chmod 770 {} \;37sudo find "$WEB_ROOT/tmp" -type f -exec chmod 660 {} \;
It SLAPs
That’s it! That’s the setup for a basic SLAP stack server. I think this is a cozy little setup that will hold up over the long term and keep my maintenance burden light.
I’m still in the experimental phase so I will write a follow-up (or edit this post) if I learn anything new, which I most assuredly will.
Message me on Mastodon or email me if you have any opinions about this setup, or if you want to tell me about your favorite server stack.
AI did not write this, I just like using em dashes, okay? ↩︎
Its market share has stagnated for the last few years, after years of growth. ↩︎
Okay, technically it can be a few files if you’re using write-ahead logging (i.e., or WAL mode). ↩︎
My long-time shared host, NearlyFreeSpeech.NET uses a networked filesystem that, while fine for most purposes, can lead to situations where the SQLite database file becomes corrupted (though I’ve found it was able to recover with minimal data loss). I needed to move to a setup that would give me a local disk with predictable file-locking. At least, that’s my vague understanding of the problem. ↩︎
I’ve been a customer for so long that I was able to get a 10 project plan at a decent price that no longer exists. Fortunately, I’m able to keep using this plan. If you’ve only got the one project, they have a free plan. But for most people I think the Solo plan should be okay. If you need more than the three projects it gives you, you might want to look elsewhere as the next plan up is a bit pricey in my opinion. ↩︎