Stick It to The Man and Self-Host Your Own Web Server

Written by theselfhoster | Published 2023/02/20
Tech Story Tags: web-development | web-hosting | apache | mariadb | php | server | self-hosting | webdev | hackernoon-es | hackernoon-hi | hackernoon-zh | hackernoon-vi | hackernoon-fr | hackernoon-pt | hackernoon-ja

TLDRSelf-hosting email for small to medium companies, business owners, non-profit organizations and individuals. There is no real limit to who may self-host, but privacy conscious groups will benefit directly and see immediate results. Small to medium companies will get a fully functioning system that adheres to all current encryption and security protocols, especially the PCI specification.via the TL;DR App

Who is this guide for?

Self-hosting web content is relevant to small to medium companies, business owners, non-profit organizations and individuals.

There is no real limit to who may self-host, but privacy conscious groups will benefit directly and see immediate results. Small to medium companies will get a fully functioning system that adheres to all current encryption and security protocols, especially the PCI specification.

Why self-hosting?

There are tons of reasons why self-hosting is beneficial, here are some of them:

  • Privacy
  • Enhanced security
  • Customization
  • Lower costs

Email hosting

This article is about web server hosting, if you are interested about email hosting then please read the article on Self-Hosting Your Own Email Server.

Prerequisites

  • A new server, dedicated or virtual, with internet access
  • A RHEL-based Linux distribution: Alma, Rocky, Oracle or Fedora Server
  • Root access

Server hostname

Please make sure your server has a fully qualified domain name (FQDN) and the /etc/hosts file has the IP of the server point to the FQDN and not the short name. This is a requirement for some tools like OpenDKIM, which will fail to work without an FQDN.

To test if your FQDN is properly setup, run:

# hostname -f
server.example.tld

If you only see the server name and not the FQDN then use the systemd hostnamectl command to set your FQDN:

# hostnamectl set-hostname server.example.tld

Step 1 - Required packages

The first step is to install some basic packages, use your package manager:

dnf for newer versions of EL8/EL9:

# dnf install php php-cli php-pdo php-mbstring epel-release unzip wget

yum for EL7:

# yum install php php-cli php-pdo php-mbstring epel-release unzip wget

for Fedora the packages are slightly different:

# dnf install php php-cli php-pdo php-mbstring unzip wget

Step 2 - Install Aetolos

In the second step we’ll install the Aetolos virtual hosting control panel, which only generates configuration files and does not make any other modifications to the system:

# wget -O /root/master.zip 'https://gitlab.com/noumenia/aetolos/-/archive/master/aetolos-master.zip'
# unzip /root/master.zip -d /root/
# mv /root/aetolos-master /root/aetolos
# rm -rf /root/master.zip

The latest development code can be downloaded from the git repository:

git clone 'https://gitlab.com/noumenia/aetolos.git'

Step 3 - Enable services

Now it is time to choose what services the server will provide. Run Aetolos with the status parameter to get an idea of all the available services:

# ~/aetolos/aetolos --status
+---------+--------------+---------+------------+--------------+
| Enabled | Module       | SystemD | Repository | Dependencies |
+---------+--------------+---------+------------+--------------+
| [ ]     | apache       |         |            |              |
| [ ]     | clamav       |         | epel       | postfix      |
| [ ]     | dehydrated   |         |            |              |
| [ ]     | dovecot      |         |            |              |
| [ ]     | haproxy      |         |            |              |
| [ ]     | mariadb      |         |            |              |
| [ ]     | mtasts       |         |            | apache       |
| [ ]     | nsd          |         | epel       |              |
| [ ]     | opendkim     |         | epel       | postfix      |
| [ ]     | opendmarc    |         | epel       | postfix      |
| [✓]     | php          |         |            |              |
| [ ]     | postfix      |         |            |              |
| [ ]     | postgrey     |         | epel       | postfix      |
| [ ]     | spamassassin |         |            | postfix      |
| [✓]     | virtualhost  |         |            |              |
+---------+--------------+---------+------------+--------------+

As this is going to be a self-hosted web server, we are going to enable the services that give us a complete set of features for web hosting:

  • Apache, a web server for responding on ACME requests
  • Dehydrated, a bash script for Let’s Encrypt certificates
  • MariaDB, the database

If you have a separate server for email with DKIM signatures, you should also enable OpenDKIM in this server as well. Then share the key among servers for proper signing.

Let’s enable these services with Aetolos:

# ~/aetolos/aetolos --enable=apache
# ~/aetolos/aetolos --enable=dehydrated
# ~/aetolos/aetolos --enable=mariadb

Now we may re-run Aetolos with the status parameter to see the following table:

+---------+--------------+---------+------------+--------------+
| Enabled | Module       | SystemD | Repository | Dependencies |
+---------+--------------+---------+------------+--------------+
| [✓]     | apache       | dead    |            |              |
| [ ]     | clamav       |         | epel       | postfix      |
| [✓]     | dehydrated   |         |            |              |
| [ ]     | dovecot      |         |            |              |
| [ ]     | haproxy      |         |            |              |
| [✓]     | mariadb      | dead    |            |              |
| [ ]     | mtasts       |         |            | apache       |
| [ ]     | nsd          |         | epel       |              |
| [ ]     | opendkim     |         | epel       | postfix      |
| [ ]     | opendmarc    |         | epel       | postfix      |
| [✓]     | php          | dead    |            |              |
| [ ]     | postfix      |         |            |              |
| [ ]     | postgrey     |         | epel       | postfix      |
| [ ]     | spamassassin |         |            | postfix      |
| [✓]     | virtualhost  |         |            |              |
+---------+--------------+---------+------------+--------------+

Note the SystemD column which shows most services as dead. This is not an issue, because we have not configured/started those services, yet.

Step 4 - Setup

Finally, we may run Aetolos setup to install the required RPM packages and generate all the configuration files. We prefer to see the verbose output with the extra --verbose parameter.

You will notice a warning about the dehydrated script, which tells us to specify the registration email address for Let’s Encrypt

# ~/aetolos/aetolos --verbose --setup
[DEBUG] Check running system
[DEBUG] Detected: AlmaLinux 8.7 (Stone Smilodon)
[DEBUG] Check system memory
[DEBUG] Detected: 460MiB
[DEBUG] Check CPU cores
[DEBUG] Detected: 2
[DEBUG] Check proxy
[DEBUG] Loading Aetolos configuration
[DEBUG] Starting operating system setup
[DEBUG] Checking repository dependencies
[DEBUG] Checking package dependencies
[DEBUG] Installing package: php-fpm
[DEBUG] Checking SELinux requirements
[DEBUG] SELinux: enable httpd_can_network_connect
[DEBUG] SELinux: enable httpd_can_sendmail
[DEBUG] SELinux: enable httpd_read_user_content
[DEBUG] SELinux: enable httpd_enable_homedirs
[DEBUG] SELinux: enable httpd_home_tmp_aetolos.pp
[DEBUG] Checking certificate requirements
[DEBUG] Generating a self-signed certificate
[DEBUG] Certificate: /etc/pki/tls/certs/localhost.crt
[DEBUG] Fullchain: /etc/pki/tls/certs/localhost.fullchain
[DEBUG] Key: /etc/pki/tls/private/localhost.key
[DEBUG] Checking ACME client
[DEBUG] Downloading ACME client: dehydrated 0.7.1
[DEBUG] Installing ACME client under: /root/dehydrated/
[WARNING] ACME registration email set to: postmaster
[WARNING] Please change the ACME registration email by executing: '/root/aetolos/aetolos --verbose --module=dehydrated --registration-email=new-email'
[DEBUG] Checking module dependencies
[DEBUG] Save configuration: apache
[DEBUG] Writing to file: /etc/httpd/conf/httpd.conf
[DEBUG] Writing to file: /etc/httpd/conf.d/ssl.conf
[DEBUG] Writing to file: /etc/sysconfig/htcacheclean
[INFO] System memory: 460MB
[INFO] Allocate 50% of system memory to MariaDB: 230MB
[INFO] 70% of the above usage goes to total thread memory: 161MB
[INFO] The rest 30% goes to server buffers: 69MB
[INFO] MariaDB memory per connection: 18MB
[INFO] MariaDB max connections: 10
[INFO] MariaDB temporary table size: 16MB
[INFO] MariaDB query cache: 16MB
[INFO] Allocate 40% of system memory to Apache/PHP-FPM: 184MB
[INFO] Estimated average number of connections per CPU core: 9
[INFO] Apache StartServers: 1
[INFO] Apache ServerLimit: 2
[INFO] Apache ThreadsPerChild: 18
[INFO] Apache MinSpareThreads: 18
[INFO] Apache ThreadLimit: 22
[INFO] Apache MaxRequestWorkers: 36
[INFO] Apache maximum number of concurrent connections: 108
[DEBUG] Writing to file: /etc/logrotate.d/httpd
[DEBUG] Save configuration: dehydrated
[DEBUG] Writing to file: /root/dehydrated/config
[DEBUG] Writing to file: /root/dehydrated/hook.sh
[DEBUG] Writing to file: /etc/pki/letsencrypt/domains.txt
[DEBUG] Writing to file: /etc/cron.daily/dehydrated.cron
[DEBUG] Save configuration: mariadb
[DEBUG] Writing to file: /etc/systemd/system/mariadb.service.d/limitnofile.conf
[DEBUG] Writing to file: /etc/my.cnf.d/mariadb-server.cnf
[DEBUG] Save configuration: php
[DEBUG] Writing to file: /etc/php.ini
[DEBUG] Writing to file: /etc/php.d/10-opcache.ini
[DEBUG] Save configuration: virtualhost
[DEBUG] Writing to file: /etc/tmpfiles.d/hometmp.conf
[DEBUG] Reloading systemd

As per the warning, we add an email address for Let’s Encrypt:

# ~/aetolos/aetolos --verbose --module=dehydrated [email protected]

Start MariaDB

Unlike other services, MariaDB needs to be started and working, for Aetolos to add virtual hosts and the corresponding database permissions. So lets enable and start MariaDB:

# systemctl --now enable mariadb

At this point we may secure the MariaDB instance by running:

/usr/bin/mysql_secure_installation

This requires that you also create the /root/.my.cnf file with the login details for root:

[client]
default-character-set="utf8mb4"
user="root"
password='the_password_set_in_the_previous_step'

Add virtual hosts

Now we are ready to add as many virtual hosts as we like (or as many as the server can handle).

Before running any commands, we may take a second to think which virtual hosts will have a prefix subdomain, sometimes we may have a domain like example.tld with a www subdomain (www.example.tld), other times we may not want to use a prefix because we want our email addresses only at the top domain ([email protected] and not [email protected]).

As an example, we will add three virtual hosts, two of them without a subdomain:

# ~/aetolos/aetolos --verbose --module=virtualhost --add-virtualhost=example1.tld --no-prefix
[DEBUG] Verify domain: example1.tld
[DEBUG] Add virtual host: example1.tld
# ~/aetolos/aetolos --verbose --module=virtualhost --add-virtualhost=example2.tld --no-prefix
[DEBUG] Verify domain: example2.tld
[DEBUG] Add virtual host: example2.tld
# ~/aetolos/aetolos --verbose --module=virtualhost --add-virtualhost=example3.tld
[DEBUG] Verify domain: example3.tld
[DEBUG] Add virtual host: example3.tld

We can now list all virtual hosts:

# ~/aetolos/aetolos --module=virtualhost --list-virtualhosts
+----+---------------------+--------------+-------------+----------------+--------+--------------+
| ID | Timestamp           | Virtual host | System user | MariaDB prefix | Parked | Prefix alias |
+----+---------------------+--------------+-------------+----------------+--------+--------------+
| 1  | 2023-01-10 08:51:36 | example1.tld | example1tld | example1       | [ ]    |              |
| 2  | 2023-01-10 08:51:40 | example2.tld | example2tld | example2       | [ ]    |              |
| 3  | 2023-01-10 08:51:45 | example3.tld | example3tld | example3       | [ ]    | www          |
+----+---------------------+--------------+-------------+----------------+--------+--------------+

Each virtual host will “live” isolated within its own home directory, with its own user/group ownership, so lets take a look at our /home:

# ls -la /home
total 0
drwxr-xr-x.  5 root        root         63 Jan 20 10:51 .
dr-xr-xr-x. 17 root        root        224 Dec 21  2021 ..
drwx--x--x.  8 example1tld example1tld 159 Jan 10 08:51 example1tld
drwx--x--x.  8 example2tld example2tld 159 Jan 10 08:51 example2tld
drwx--x--x.  8 example3tld example3tld 159 Jan 10 08:51 example3tld

Setup

After we made modifications with the Aetolos command, we need to generate new configuration files for all the relevant services (Apache, PHP-FPM, Dehydrated, etc). Thus, we execute Aetolos setup one last time.

# ~/aetolos/aetolos --setup

TLS certificates

At this point all services will use the same self-signed certificate, thus it is now a good time to run dehydrated manually to gather all virtual host certificates. Since Aetolos uses the ACME protocol with domain validation via HTTP port 80, we first start our Apache server to service those requests.

# systemctl start httpd
# ~/dehydrated/dehydrated --cron --config /root/dehydrated/config --keep-going
# systemctl stop httpd
# ~/aetolos/aetolos --setup

Services

We may now enable and start all services.

# systemctl --now enable httpd [email protected] [email protected] [email protected] mariadb php-fpm
Created symlink /etc/systemd/system/multi-user.target.wants/[email protected] → /usr/lib/systemd/system/[email protected]
Created symlink /etc/systemd/system/multi-user.target.wants/[email protected] → /usr/lib/systemd/system/[email protected]
Created symlink /etc/systemd/system/multi-user.target.wants/[email protected] → /usr/lib/systemd/system/[email protected]

You will notice that each virtual host has its own Apache service, this is a feature provided by systemd and allows us to define the virtual hosts into separate systemd services. The --now parameter will start all services.

The architecture

There should be special mention about the architecture used for hosting websites. For security reasons, each virtual host is isolated within unix accounts stored under their own /home directory. Public web content should be stored within the public_html directory.

/home/example1tld                               -> virtual host home
/home/example1tld/etc                           -> email accounts
/home/example1tld/mail                          -> email storage
/home/example1tld/public_html                   -> web content
/home/example1tld/www                           -> symbolic link to public_html
/home/example1tld/tmp                           -> temp directory

Aetolos

At this point Aetolos has configured the server without any custom modifications, all changes are done on the default configuration files. We may delete Aetolos from the server and continue modifications manually or we may keep Aetolos in case we want to add more virtual hosts in the future.

Credits

Article photo by Manuel Geissinger from pexels


Written by theselfhoster | The self-hosting expert, helping the world learn about DIY hosting in the modern world of cloud computing.
Published by HackerNoon on 2023/02/20