This guide is written with the general understanding that the reader is mostly familiar with how to access and setup a VPS Linus server. Most commands will be done over an SSH connection, some files will be uploaded with SCP. In this guide please replace all occurrences of “example.com” with your actual hostname.

I recently setup a new mail server which also needed a web server installed and thought I would throw up a guide on setting up virtual hosts on a Linux server, specifically CentOS Rocky 9.

There is a free and easy way to obtain the required SSL certificates (through the Let’s Encrypt project) for this, which will be covered below. The downside to using Let’s Encrypt is that certificates are only valid for 3 months and will need to be renewed. However, if you don’t mind renewing the SSL certificates every few months, or setup an automated method, Let’s Encrypt will work well. A semi automated SSL auto renewal script will be provided below and can be setup to be fully automated with not too much more effort.

In order to make it easy to add more than one website later we will setup vhosts. The following vhosts configuration method I have been using for years and tends to make multiple website management easy to manage and keep track of.

I have seen and tried various methods of mapping websites to home folders and such and this configuration can be tweaked to fit that scenario if desired. I have found it much easier, from an organization standpoint, to keep everything in one place under

/var/www/vhosts

With this method each website’s home folder will be located in a structure such as
/var/www/vhosts/example.com/html
This will allow for the document root to be in the html folder, while allowing any other files related to the website, but not being directly accessible to be placed one level higher in the folder

/var/www/vhosts/example.com

Step 0: Register the server’s IP address with your domain (or subdomain) name

When someone types your hostname, such as “example.com” into their browser, we would like that “example.com” to point to your server’s IP address. This can be done with the company managing your domain name, usually through a configuration interface on their homepage.

Step 1: Update the server

First up, starting off with a fresh CentOS Rocky 9 VPN server install we will want to update the system using the following commands after SSHing into your server.

yum update

Step 2: Install the required packages

yum install httpd mod_ssl

Step 3: Create the directory structure

mkdir /etc/httpd/conf/extra
mkdir /etc/httpd/conf/virtual
mkdir /var/www/vhosts

Step 4: Create the root folder for our website

mkdir -p /var/www/vhosts/example.com/html

Step 5: Add a placeholder for index.html

Until we get a website established and running it would be a good idea to provide a placeholder index.html file to avoid the CentOS default page showing up.

echo "Welcome to example.com" > /var/www/vhosts/example.com/html/index.html

Step 6: Create the virtual host config file(s)

Each virtual website will need a configuration file and while this can be named anything. I usually name it by the hostname and append ssl_ if it is for the https configuration. The http and https configuration files are separate (although they could be combined in one file, but this allows disabling SSL a lot easier by just renaming the SSL configuration file). Also, if configuring for https we will need to either already have SSL certificates or can obtain them for free from the Let’s Encrypt project (which will be covered below)

/etc/httpd/conf/virtual/example.com.conf
<VirtualHost _default_:80>
        ServerName example.com
        DocumentRoot /var/www/vhosts/example.com/html
        <Directory "/var/www/vhosts/example.com/html">
                Options FollowSymLinks MultiViews ExecCGI
                AllowOverride All
                Order allow,deny
                Allow from all
                Options -MultiViews
        </Directory>
        ErrorLog logs/example.com-error_log
        #CustomLog logs/example.com-access_log common
        CustomLog logs/mailbox.drassal.net-access_log combinedio
</VirtualHost>
/etc/httpd/conf/virtual/ssl_example.com.conf
# Secure Shell ver.
<VirtualHost *:443>
    #ServerAdmin webmaster@example.com
    DocumentRoot /var/www/vhosts/example.com/html
    ServerName example.com:443

    #   SSL Engine Switch:
    #   Enable/Disable SSL for this virtual host.
    SSLEngine on

    #   SSL Protocol support:
    # List the enable protocol levels with which clients will be able to
    # connect.  Disable SSLv2 access by default:
    SSLProtocol all -SSLv2 -SSLv3 -TLSv1 -TLSv1.1

    #   SSL Cipher Suite:
    # List the ciphers that the client is permitted to negotiate.
    # See the mod_ssl documentation for a complete list.
    #SSLCipherSuite ALL:!ADH:!EXPORT:!SSLv2:RC4+RSA:+HIGH:+MEDIUM:+LOW

    SSLCipherSuite          ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA

    SSLHonorCipherOrder     on

    #   Server Certificate:
    # Point SSLCertificateFile at a PEM encoded certificate.  If
    # the certificate is encrypted, then you will be prompted for a
    # pass phrase.  Note that a kill -HUP will prompt again.  A new
    # certificate can be generated using the genkey(1) command.
    #SSLCertificateFile /etc/pki/tls/certs/example.com.crt
    SSLCertificateFile /home/autossl/ssl/example.com/example.com.crt

    #   Server Private Key:
    #   If the key is not combined with the certificate, use this
    #   directive to point at the key file.  Keep in mind that if
    #   you've both a RSA and a DSA private key you can configure
    #   both in parallel (to also allow the use of DSA ciphers, etc.)
    #SSLCertificateKeyFile /etc/pki/tls/private/example.com.key.pem
    SSLCertificateKeyFile /home/autossl/ssl/example.com/example.com.key.pem

    #   Server Certificate Chain:
    #   Point SSLCertificateChainFile at a file containing the
    #   concatenation of PEM encoded CA certificates which form the
    #   certificate chain for the server certificate. Alternatively
    #   the referenced file can be the same as SSLCertificateFile
    #   when the CA certificates are directly appended to the server
    #   certificate for convinience.
    #SSLCertificateChainFile /etc/pki/tls/certs/example.com.rootbundle.crt
    SSLCertificateChainFile /home/autossl/ssl/example.com/example.com.rootbundle.crt

    #   Certificate Authority (CA):
    #   Set the CA certificate verification path where to find CA
    #   certificates for client authentication or alternatively one
    #   huge file containing all of them (file must be PEM encoded)
    #SSLCACertificateFile /etc/pki/tls/certs/ca-bundle.crt

    <Directory "/var/www/vhosts/example.com/html">
        Options Indexes FollowSymLinks
        AllowOverride All
        Order allow,deny
        Allow from all
    </Directory>
    ErrorLog logs/ssl_example.com-error_log
    #CustomLog logs/ssl_example.com_access_log common
    CustomLog logs/ssl_example.com-access_log "%h %l %u %t \"%!414r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %I %O %{SSL_PROTOCOL}x %{SSL_CIPHER}x"
    #CustomLog logs/ssl_request_log \
    #      "%t %h %{SSL_PROTOCOL}x %{SSL_CIPHER}x \"%r\" %b"
</VirtualHost>

Step 7: Disable the SSL configuration

If you don’t already have the certificate for the SSL configuration then it will need to be disabled since the SSL certificates are required when starting up the httpd service. If they are not present the httpd service will fail to start with an error about the certificates.

mv /etc/httpd/conf/virtual/ssl_example.com.conf /etc/httpd/conf/virtual/ssl_example.com.confx

Step 8: Startup the httpd service

Now that configuration is setup, configuration files created, and directory structure created it is time to startup the httpd service (the http server). This will bring up the http server on port 80 only, then we can use that to request an SSL certificate from Let’s Encrypt. Startup the httpd service and hopefully there are no errors. If there are errors “systemctl status httpd” can be used to see a little more detail.

systemctl start httpd

If everything goes well you should be able to open up your web browser and enter your hostname. The text in the index.html file we created earlier should be visible in your web browser. If not, perhaps there is a firewall rule either on the server, or with the VPS company that needs to be configured. In my case there is a firewall control panel for the VPS server, I need to add a rule to allow packets through on port 80 (http) and port 443 (https).

Let’s Encrypt will verify it is our website by requesting we place a specific file with specific contents on the web server. Let’s Encrypt will then verify that file exists on our server (verifying we have access to the server to have created the file) and proceed to sign the certificate for us.

Step 9: Setup the SSL certificate using Let’s Encrypt

While you can run the Python script to retrieve the SSL certificate on the root account it would be preferable to run it on a more limited account. This way, if there is a bug in the script the damage it can cause will be limited.

In this configuration I created an account called “autossl” to run the SSL retrieval script under. Let’s create that account below. We won’t be logging into it normally (with a password) so we can just create it and leave it at that. We will be placing the SSL script in the “autossl” user’s home directory.

useradd autossl

Next, we will use the below script that is based on Python to automagically retrieve an SSL certificate from Let’s Encrypt. The SSL certificate can also be done in a less automated, more interactive way which I am considering covering in an upcoming post. A handy script/website (which works entirely offline) is at the following address, and I used it many times before developing this automated script.

https://gethttpsforfree.com (but we will be using the below automated method unless manual is desired)

For now grab this script and we will upload it to the /home/autossl folder. Since you will be running this script on your server I would suggest going over and being familiar with what it is doing, it is all in plain readable source code text. Most of the script is written in Python with some in BASH (.sh) scripts. It is easily customizable if desired.

Here is a brief explanation of the files contained within the archive.

account.key.pemYour pesonal private key
renew_example.com.shThe SSL certificate renew/retrieval script
script/acme_tiny.pyContains the Let’s Encrypt server interaction fucntions
script/argparse.pyRequired for script/acme_tiny.py
script/renew_cert.shWrapper/abstraction for script/acme_tiny.py
script/splitter.pySplits the file from Let’s Encrypt into the server certificate and root certificate
When the script is run for the first time a folder named after the website will be created along with a folder inside there named after the current day (YYYYMMDD).

The first thing we need to do is rename the file renew_example.com.sh to match your hostname. While not required it does make it easier to remember which hostname the script is associated with.

If it is desired to add a second virtual host, just duplicate the renew_exmample.com.sh file and name it appropriately.

cd /home/autossl/ssl
mv renew_exmample.com.sh renew_mydomain.com.sh

Next open up that file that we just renamed with your preference of editor (such as vi, vim, or nano) and on the top line you will see a line that looks like the following:

vi renew_mydomain.com.sh
SITENAME="example.com"

Change example.com to your hostname, this is the hostname (common name) that will be put on the SSL certificate.

SITENAME="mydomain.com"

Also during the SSL certificate verification process the Let’s Encrypt server will access our server (at the domain/hostname we setup here) and attempt to verify it (by checking for a specific file, in a specific location, with a specific string in it). We need to grant permission for the “autossl” account (or anyone) to write to this folder. This folder location is below so be sure to create this folder structure then grant permissions as follows. The “chmod 777” will allow any user to write to this folder.

mkdir -p /var/www/vhosts/example.com/html/.well-known/acme-challenge
chmod 777 /var/www/vhosts/example.com/html/.well-known/acme-challenge

If more strict permissions are desired then the below commands can be used instead, however, only the autossl (and root) user will be able to write to this location.

mkdir -p /var/www/vhosts/example.com/html/.well-known/acme-challenge
chown autossl:autossl /var/www/vhosts/example.com/html/.well-known/acme-challenge
chmod 755 /var/www/vhosts/example.com/html/.well-known/acme-challenge

After we setup these above things we need to generate our account private key which can be done with the below command. This only has to be done once and this will now be your private key used in all future requests with Let’s Encrypt. Keep this key secret as it can be used to revoke the certificate and other things.

openssl genrsa 4096 > account.key.pem

Place this above file in the /home/autossl/ssl directory making sure that the “autossl” account has access to it. It would be good to also restrict permissions using the below two commands. Also let’s run one more command to cleanup permissions so the “autossl” user has correct permissions.

chown autossl:autossl /home/autossl/ssl/account.key.pem
chmod 600 /home/autossl/ssl/account.key.pem
chown -R autossl:autossl /home/autossl/ssl

Next we are ready to retrieve our certificate, assuming the web server is up and running, and the firewall rules are setup correctly. We will change to the “autossl” user and then run the SSL retrieval script.

su autossl
cd ~/ssl
./renew_example.com.sh

The script should run and, if everything goes correctly, have placed the new certificates under the following directory:

/home/autossl/ssl/example.com/YYYYMMDD
*where example.com is your domain/hostname and YYYYMMDD is today's date.

Next we need to exit out of the “autossl” user to get back to root privileges, enable SSL on our website, and restart the httpd service.

exit
mv /etc/httpd/conf/virtual/ssl_example.com.confx /etc/httpd/conf/virtual/ssl_example.com.conf

Step 10: Restart httpd service

systemctl restart httpd

If all goes well the httpd service will restart with SSL enabled and your website should be accessable with https://example.com now. If something went wrong the following command can be used to show a little more detail:

systemctl status httpd

I am planning on making a video later that goes through this whole process so be on the lookout for that and I will be providing a link here.

Leave a Reply

Your email address will not be published. Required fields are marked *