Introduction
The focus of this howto is for those users who need to host their own domains and maybe a few customer domains. This is not aimed at being used for mass web hosting.
There are many ways to do virtual websites under linux. The technique I am going to use is multiple domains on one ip address. I’m using standard linux users to log into the virtual domains.
Setting Up The Base Server
For a dedicated server start with the base server setup:
Howto Ubuntu 22.04 Base Server Setup
Introduction All of our servers will start with this install. This base server is based on Ubuntu 22.04 LTS Server. I don’t explain much in the howto so if you have a question leave a comment or use Google. Downloading … Continue readingNOTE: If you don’t follow the base server setup then you may run into problems with this howto.
Install Software
We need to install an FTP server and Let’s Encrypt. So type the following:
> sudo add-apt-repository ppa:certbot/certbot
> sudo apt update
> sudo apt install vsftpd python3-certbot-apache
Setup Default User Directory
A new user’s directory needs to have some files and folders created for them. We will modify the user skel directory so when a new user is created the required folder structure will be there.
Type the following.
> sudo mkdir -p /etc/skel/{website,logs,cgi-bin,backup}
> sudo echo “HELLO WORLD” > index.html
> sudo mv index.html /etc/skel/website/
Configuring vsftpd
Lets create the configuration file. Replace the contents of /etc/vsftpd.conf with the text below.
listen=NO listen_ipv6=YES anonymous_enable=NO local_enable=YES write_enable=YES #local_umask=022 dirmessage_enable=YES use_localtime=YES xferlog_enable=YES connect_from_port_20=YES chroot_local_user=YES secure_chroot_dir=/var/run/vsftpd/empty pam_service_name=vsftpd rsa_cert_file=/etc/ssl/certs/ssl-cert-snakeoil.pem rsa_private_key_file=/etc/ssl/private/ssl-cert-snakeoil.key ssl_enable=NO allow_writeable_chroot=YES pasv_enable=Yes pasv_min_port=40000 pasv_max_port=40100
Start vsftpd.
> sudo systemctl enable vsftpd
> sudo systemctl start vsftpd.service
Configuring Apache
Most of the apache configuration is already done. We are going to do some changes to make managing websites easier.
Create the virtual host config file. I defined macros to make virtual host creation easier. I also turn on compression. Create /etc/apache2/conf-available/virtual.conf with the following:
# Go ahead and accept connections for these vhosts # from non-SNI clients SSLStrictSNIVHostCheck off # define a macro for the virtual hosts # the user's directory should be setup as follows: # |- cgi-bin # |- logs # |- website # |- ssl # LoadModule macro_module modules/mod_macro.so <Macro virtHost $type $user $host> use $type $host ServerName $host ServerAlias www.$host DocumentRoot /home/$user/website ScriptAlias "/cgi-bin/" "/home/$user/cgi-bin" LogFormat "%h %l %u %t \"%r\" %>s %b" common CustomLog /home/$user/logs/access_log common ErrorLog /home/$user/logs/error_log <Directory /home/$user/website> DirectoryIndex index.html index.php Options Indexes FollowSymLinks AllowOverride All Require all granted # setup file compression use CompressFiles # setup browser caching use BrowserCache # disable hotlinking for some files use DisableHotLink $host </Directory> </VirtualHost> </Macro> <Macro BrowserCache> # Enable expires cache <IfModule mod_expires.c> ExpiresActive On ExpiresByType text/css "access 1 month" ExpiresByType text/html "access 1 month" ExpiresByType image/gif "access 1 year" ExpiresByType image/png "access 1 year" ExpiresByType image/jpg "access 1 year" ExpiresByType image/jpeg "access 1 year" ExpiresByType image/x-icon "access 1 year" ExpiresByType application/pdf "access 1 month" ExpiresByType application/javascript "access 1 month" ExpiresByType text/x-javascript "access 1 month" ExpiresDefault "access 1 month" </IfModule> # Cache-Control Headers <ifModule mod_headers.c> <filesMatch "\.(ico|jpe?g|png|gif|swf)$"> Header set Cache-Control "public" </filesMatch> <filesMatch "\.(css)$"> Header set Cache-Control "public" </filesMatch> <filesMatch "\.(js)$"> Header set Cache-Control "private" </filesMatch> <filesMatch "\.(x?html?|php)$"> Header set Cache-Control "private, must-revalidate" </filesMatch> </ifModule> </Macro> <Macro CompressFiles> # enable compression <IfModule mod_deflate.c> AddOutputFilterByType DEFLATE "application/atom+xml" AddOutputFilterByType DEFLATE "application/javascript" AddOutputFilterByType DEFLATE "application/json" AddOutputFilterByType DEFLATE "application/ld+json" AddOutputFilterByType DEFLATE "application/manifest+json" AddOutputFilterByType DEFLATE "application/rdf+xml" AddOutputFilterByType DEFLATE "application/rss+xml" AddOutputFilterByType DEFLATE "application/schema+json" AddOutputFilterByType DEFLATE "application/vnd.geo+json" AddOutputFilterByType DEFLATE "application/vnd.ms-fontobject" AddOutputFilterByType DEFLATE "application/x-font" AddOutputFilterByType DEFLATE "application/x-font-opentype" AddOutputFilterByType DEFLATE "application/x-font-otf" AddOutputFilterByType DEFLATE "application/x-font-truetype" AddOutputFilterByType DEFLATE "application/x-font-ttf" AddOutputFilterByType DEFLATE "application/x-javascript" AddOutputFilterByType DEFLATE "application/x-web-app-manifest+json" AddOutputFilterByType DEFLATE "application/xhtml+xml" AddOutputFilterByType DEFLATE "application/xml" AddOutputFilterByType DEFLATE "font/eot" AddOutputFilterByType DEFLATE "font/otf" AddOutputFilterByType DEFLATE "font/ttf" AddOutputFilterByType DEFLATE "font/opentype" AddOutputFilterByType DEFLATE "image/bmp" AddOutputFilterByType DEFLATE "image/svg+xml" AddOutputFilterByType DEFLATE "image/vnd.microsoft.icon" AddOutputFilterByType DEFLATE "image/x-icon" AddOutputFilterByType DEFLATE "text/cache-manifest" AddOutputFilterByType DEFLATE "text/css" AddOutputFilterByType DEFLATE "text/html" AddOutputFilterByType DEFLATE "text/javascript" AddOutputFilterByType DEFLATE "text/plain" AddOutputFilterByType DEFLATE "text/vcard" AddOutputFilterByType DEFLATE "text/vnd.rim.location.xloc" AddOutputFilterByType DEFLATE "text/vtt" AddOutputFilterByType DEFLATE "text/x-component" AddOutputFilterByType DEFLATE "text/x-cross-domain-policy" AddOutputFilterByType DEFLATE "text/xml" </IfModule> </Macro> <Macro DisableHotLink $host > # Disable file hotlinking - jpg jpeg png gif pdf <IfModule mod_rewrite.c> RewriteEngine on RewriteCond %{HTTP_REFERER} !^$ RewriteCond %{HTTP_REFERER} !^http(s)?://(www\.)?$host [NC] RewriteCond %{HTTP_REFERER} !^http(s)?://(www\.)?google.com [NC] RewriteRule \.(jpg|jpeg|png|gif|pdf)$ – [NC,F,L] </IfModule> </macro> <Macro VHost443 $host > <VirtualHost *:443> SSLEngine on SSLProtocol all -SSLv2 SSLCipherSuite HIGH:MEDIUM:!aNULL:!MD5 SSLCertificateFile /etc/letsencrypt/live/$host/cert.pem SSLCertificateKeyFile /etc/letsencrypt/live/$host/privkey.pem SSLCertificateChainFile /etc/letsencrypt/live/$host/fullchain.pem </Macro> <Macro VHost80 $host > <VirtualHost *:80> </Macro>
Enable the configuration.
> sudo a2enconf virtual
Enable macros and ssl.
> sudo a2enmod macro
> sudo a2enmod ssl
Restart apache
> sudo service apache2 restart
Configuring Let’s Encrypt
Let’s Encrypt needs to be configured to auto renew certs. Lets create a daily cron job
> sudo nano -w /etc/cron.daily/letsencrypt
#!/usr/bin/bash # letsencrypt auto renew /usr/bin/certbot renew --no-self-upgrade >> /var/log/le-renew.log
Adding a Default Website
Now we will create a default website. This site will be used when no other website can be found.
Setup a DNS record for the new domain. I won’t cover this here.
Add a user. This user will be associated with the new domain name. Type the following.
> sudo useradd -m -U -s /bin/bash -c “default website” defaultweb
> sudo passwd defaultweb
Add the apache user to the new user’s group.
> sudo usermod -a -G defaultweb www-data
Update directory permissions.
> sudo chmod g+rwx /home/defaultweb
> sudo chown -R defaultweb:defaultweb /home/defaultweb
Create the virtual host file. For the default server we will use port 80. Past the text below into the file. Type:
> sudo nano -w /etc/apache2/sites-available/00-default.conf
# Virtual host config file # # MACRO FORMAT # virtHost [type] [user] [host] # type = VHost80 or VHost443 # user = the username of the website # host = domain name or virtual host name # # Use the line below to configure a site on port 80 use virtHost VHost80 defaultweb myserver.mydomain.tld # Uncomment the line below once lets encrypt is setup # use virtHost VHost443 defaultweb myserver.mydomain.tld
Disable the old default site and enable our default site.
> sudo a2dissite 000-default
> sudo a2ensite 00-default
Reload apache config
> sudo service apache2 reload
Test out the new website. You should get a page that says ‘Hello World’.
Now we will setup lets encrypt for the default website. The website must be reachable from the internet. So lets get the cert:
> sudo certbot certonly –webroot -w /home/defaultweb/website/ -d <YOUR_DOMAIN> –email <YOUR_EMAIL_ADDRESS> –agree-tos
Edit /etc/httpd/virtualHosts.d/00-default.conf
Uncomment the last line to enable ssl connections for the virtual host.
Reload apache.
> sudo service apache2 reload
Test it out. Connect to your default host via https.
Setup Additional Virtual Hosts
Adding a new virtual host is like adding the default virtual host. Lets go through the steps.
Be sure DNS is configured for the new virtual host.
Setup a new user. This user will be associated with the new domain name. Type the following. Change ‘NEWUSER’ to the username you want.
> sudo useradd -m -U -s /bin/bash -c “Virtual Website User” NEWUSER
> sudo passwd NEWUSER
> sudo usermod -a -G NEWUSER www-data
> sudo chmod g+rwx /home/NEWUSER
> sudo chown -R NEWUSER:NEWUSER /home/NEWUSER
Create the virtual host file. For the virtual server we will use port 80. Past the text below into the file. Replace ‘NEWUSER’ with your user name. Replace NEWVHOST with your hostname.
> sudo nano -w /etc/apache2/sites-available/NEWUSER.conf
# Virtual host config file # # MACRO FORMAT # virtHost [type] [user] [host] # type = VHost80 or VHost443 # user = the username of the website # host = domain name or virtual host name # # Use the line below to configure a site on port 80 use virtHost VHost80 NEWUSER NEWVHOST # Uncomment the line below once lets encrypt is setup # use virtHost VHost443 NEWUSER NEWVHOST
Enable the new site and reload apache config
> sudo a2ensite NEWUSER
> sudo service apache2 reload
Now we will setup lets encrypt for the new website. The website must be reachable from the internet. Replace NEWUSER and NEWVHOST with the info you have. So lets get the cert.
> sudo certbot certonly –webroot -w /home/NEWUSER/website/ -d NEWHOST -d www.NEWHOST –email YOUR_EMAIL_ADDRESS –agree-tos
Edit /etc/httpd/virtualHosts.d/NEWUSER.conf
Uncomment the last line to enable ssl connections for the virtual host.
Reload apache.
> sudo service apache2 reload
Test it out. Connect to your new host via https.
Conclusion
That’s the complete setup.