For quite a few years I had two VDS instances, each one hosting a different website. And I always wanted to host both websites at the same server, so I could reduce my costs and save time maintaining two servers instead of one. Yesterday I finally read some manual and did it.

Apache and 2 websites

It’s actually not that complicated, but countless articles and manuals on the subject were making it to look so for me.

Apache configuration

So, you have two websites (http://roskomnadzor.ru and http://wetpussy.com) and you want to host them on the same server using Apache.

Suppose you put them here:

$ tree /var/www/ -L 2 -a
/var/www/
|-- roskomnadzor
|   |-- .htaccess
|   |-- index.php
|   |-- robots.txt
|   |-- wp-admin
|   |-- wp-content
|   `-- ...
`-- wetpussy
    |-- .htaccess
    |-- images
    |-- includes
    |-- index.php
    |-- robots.txt
    `-- ...

Let’s take a look at Apache config files we’ll need:

$ tree /etc/apache2/ -L 1
/etc/apache2/
|-- apache2.conf
|-- conf-available
|-- conf-enabled
|-- envvars
|-- magic
|-- mods-available
|-- mods-enabled
|-- ports.conf
|-- sites-available
`-- sites-enabled

You can leave the main config (apache2.conf) untouched, default settings are just fine. But here’s mine just in case:

DefaultRuntimeDir ${APACHE_RUN_DIR}

#
# PidFile: The file in which the server should record its process
# identification number when it starts.
# This needs to be set in /etc/apache2/envvars
#
PidFile ${APACHE_PID_FILE}

#
# Timeout: The number of seconds before receives and sends time out.
#
Timeout 30

#
# KeepAlive: Whether or not to allow persistent connections (more than
# one request per connection). Set to "Off" to deactivate.
#
KeepAlive On

#
# MaxKeepAliveRequests: The maximum number of requests to allow
# during a persistent connection. Set to 0 to allow an unlimited amount.
# We recommend you leave this number high, for maximum performance.
#
MaxKeepAliveRequests 30

#
# KeepAliveTimeout: Number of seconds to wait for the next request from the
# same client on the same connection.
#
KeepAliveTimeout 3


# These need to be set in /etc/apache2/envvars
User ${APACHE_RUN_USER}
Group ${APACHE_RUN_GROUP}

HostnameLookups Off

ErrorLog ${APACHE_LOG_DIR}/error.log
LogLevel warn

# Include module configuration:
IncludeOptional mods-enabled/*.load
IncludeOptional mods-enabled/*.conf

# Include list of ports to listen on
Include ports.conf

# Sets the default security model of the Apache2 HTTPD server. It does
# not allow access to the root filesystem outside of /usr/share and /var/www.
# The former is used by web applications packaged in Debian,
# the latter may be used for local directories served by the web server. If
# your system is serving content from a sub-directory in /srv you must allow
# access here, or in any related virtual host.
<Directory />
    Options FollowSymLinks
    AllowOverride None
    Require all denied
</Directory>

#<Directory /usr/share>
#    AllowOverride None
#    Require all granted
#</Directory>

<Directory /var/www/>
    Options Indexes FollowSymLinks
    AllowOverride None
    Require all granted
</Directory>

#<Directory /srv/>
#    Options Indexes FollowSymLinks
#    AllowOverride None
#    Require all granted
#</Directory>

# AccessFileName: The name of the file to look for in each directory
# for additional configuration directives.  See also the AllowOverride
# directive.
#
AccessFileName .htaccess

#
# The following lines prevent .htaccess and .htpasswd files from being
# viewed by Web clients.
#
<FilesMatch "^\.ht">
    Require all denied
</FilesMatch>

LogFormat "%v:%p %h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" vhost_combined
LogFormat "%h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" combined
LogFormat "%h %l %u %t \"%r\" %>s %O" common
LogFormat "%{Referer}i -> %U" referer
LogFormat "%{User-agent}i" agent

# Include generic snippets of statements
IncludeOptional conf-enabled/*.conf

# Include the virtual host configurations:
IncludeOptional sites-enabled/*.conf

<IfModule mpm_prefork_module>
    StartServers 1
    MinSpareServers 1
    MaxSpareServers 4
    MaxClients 4
    MaxRequestsPerChild 1000
</IfModule>

The ports.conf specifies ports which Apache listens to. If you don’t have any special setup in mind (like using NGINX as a reverse proxy for static files), keep it with default 80:

Listen 80

Virtual Hosts

As you might realize, your server has 1 IP address, but you want it to be associated with 2 websites. First of all, naturally, you need to set DNS records accordingly at your domain names registrar(s). And then you need to create config files (Virtual Hosts) for your websites.

Config for the first website /etc/apache/sites-available/001-roskomnadzor.conf:

<VirtualHost *:80>

  Servername roskomnadzor.ru
  ServerAlias roskomnadzor.ru www.roskomnadzor.ru
  DocumentRoot /var/www/roskomnadzor/
  ServerAdmin jarov@roskomnadzor.ru
  DirectoryIndex index.php

  <Directory /var/www/roskomnadzor/>
    Options Indexes FollowSymLinks MultiViews
    AllowOverride All
    Order allow,deny
    Allow from all
  </Directory>

  ErrorLog ${APACHE_LOG_DIR}/error.log
  CustomLog ${APACHE_LOG_DIR}/access.log combined

</VirtualHost>

Here we tell Apache that requests to roskomnadzor.ru should go to /var/www/roskomnadzor/ and also allow to use rules from local .htaccess config stored in the website’s directory (we’ll use that later).

Config for the second website /etc/apache/sites-available/002-wetpussy.conf:

<VirtualHost *:80>

  Servername wetpussy.com
  ServerAlias wetpussy.com www.wetpussy.com
  DocumentRoot /var/www/wetpussy/
  ServerAdmin admin@wetpussy.com
  DirectoryIndex index.php

  <Directory /var/www/wetpussy/>
    Options Indexes FollowSymLinks MultiViews
    AllowOverride All
    Order allow,deny
    Allow from all
  </Directory>

  ErrorLog ${APACHE_LOG_DIR}/error.log
  CustomLog ${APACHE_LOG_DIR}/access.log combined

</VirtualHost>

To enable these sites run a2ensite command (or just create symbolic links) and restart Apache:

a2ensite 001-roskomnadzor.conf
a2ensite 002-wetpussy.conf
systemctl restart apache2

So now your Apache folder should look like that:

$ tree /etc/apache2/ -L 2
/etc/apache2/
|-- apache2.conf
|-- conf-available
|-- conf-enabled
|-- envvars
|-- magic
|-- mods-available
|-- mods-enabled
|-- ports.conf
|-- sites-available
|   |-- 000-default.conf
|   |-- 001-roskomnadzor.conf
|   `-- 002-wetpussy.conf
`-- sites-enabled
    |-- 001-roskomnadzor.conf -> ../sites-available/001-roskomnadzor.conf
    `-- 002-wetpussy.conf -> ../sites-available/002-wetpussy.conf

Order (001-..., 002-...) does matter. The website with the lowest number becomes a default host, so if you open your server’s IP address in browser, you will see contents of the default host.

To see the current order at your server:

$ apache2ctl -S
VirtualHost configuration:
*:80                   is a NameVirtualHost
         default server roskomnadzor.ru (/etc/apache2/sites-enabled/001-roskomnadzor.conf:1)
         port 80 namevhost roskomnadzor.ru (/etc/apache2/sites-enabled/001-roskomnadzor.conf:1)
                 alias roskomnadzor.ru
                 alias www.roskomnadzor.ru
         port 80 namevhost wetpussy.com (/etc/apache2/sites-enabled/002-wetpussy.conf:1)
                 alias wetpussy.com
                 alias www.wetpussy.com
ServerRoot: "/etc/apache2"
Main DocumentRoot: "/var/www/html"
Main ErrorLog: "/var/log/apache2/error.log"
...

WordPress and Permalinks

What if one of your websites is based on WordPress and you want to have nice meaningful links on it:

WordPress and Permalinks

Choose whatever option you like and save the changes. Underneath, WordPress will add the following lines into /var/www/roskomnadzor/.htaccess:

# BEGIN WordPress

<IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteBase /
    RewriteRule ^index\.php$ - [L]
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteRule . /index.php [L]
</IfModule>

# END WordPress

Now you need to enable this module and restart Apache:

a2enmod rewrite
systemctl restart apache2