What You Will Achieve

By the end of this guide you will have one Ubuntu server running Apache, serving two or more completely separate websites, each on its own domain name. This is called name-based virtual hosting, and it is how agencies, freelancers, and SaaS builders host client sites on a single VPS without paying for a server per site.

This guide is for you if you have a VPS or dedicated server with Ubuntu 22.04 or 24.04, a couple of domain names pointed at it, and basic comfort with the Linux terminal. No Apache experience is assumed.

Prerequisites

  • Ubuntu 22.04 or 24.04 server with sudo access (a non-root user with sudo privileges).
  • Apache 2.4 — we install it in Step 1 if it is not there yet.
  • At least two domain names with A records pointing to your server's IP (for example site1.example.com and site2.example.com). DNS can take a few hours to propagate, and Step 7 shows how to test before it does.
  • A firewall you control (UFW on Ubuntu).

Step 1: Install Apache and Verify It Is Running

First, refresh the package index and install Apache, then make sure the service starts now and on every boot:

sudo apt update
sudo apt install -y apache2
sudo systemctl enable --now apache2

apt update downloads the latest package lists so you get a current Apache build. systemctl enable --now does two things at once: enable starts Apache automatically after a reboot, and --now starts it immediately.

Confirm it is up:

sudo systemctl status apache2
curl -I http://localhost/

curl -I fetches only the HTTP headers from your local server. A 200 OK response means Apache is serving. Visiting your server's IP in a browser should show the Apache2 Ubuntu Default Page.

Step 2: Create a Separate Folder for Each Website

Every virtual host gets its own DocumentRoot — the directory Apache serves files from for that site. Keeping them separate is what isolates the sites from each other:

sudo mkdir -p /var/www/site1.example.com/public_html
sudo mkdir -p /var/www/site2.example.com/public_html

mkdir -p creates the full path including parents, and does nothing if the folders already exist.

Now hand ownership of each site's folder to the www-data user — the account Apache runs as on Ubuntu:

sudo chown -R www-data:www-data /var/www/site1.example.com/public_html
sudo chown -R www-data:www-data /var/www/site2.example.com/public_html
sudo chmod -R 755 /var/www

If the folders were owned by root, Apache could not read new files you add later and visitors would hit 403 Forbidden errors. 755 means the owner can write while everyone (including Apache) can read and traverse directories — the standard safe permission set for web roots.

Drop a tiny test page into each site so you can tell them apart later:

echo "<h1>Site 1 is working</h1>" | sudo tee /var/www/site1.example.com/public_html/index.html
echo "<h1>Site 2 is working</h1>" | sudo tee /var/www/site2.example.com/public_html/index.html

Step 3: Create the Virtual Host Configuration Files

On Ubuntu, Apache reads site configs from /etc/apache2/sites-available/. Create one file per site. Here is the config for the first site:

sudo nano /etc/apache2/sites-available/site1.example.com.conf

Paste this in:

<VirtualHost *:80>
    ServerName site1.example.com
    ServerAlias www.site1.example.com
    DocumentRoot /var/www/site1.example.com/public_html

    ErrorLog ${APACHE_LOG_DIR}/site1.example.com-error.log
    CustomLog ${APACHE_LOG_DIR}/site1.example.com-access.log combined
</VirtualHost>

What each directive does:

  • ServerName — the primary domain this block answers for. Apache matches the Host header of each incoming request against this.
  • ServerAlias — extra names for the same site, typically the www variant.
  • DocumentRoot — where this site's files live.
  • ErrorLog / CustomLog — per-site log files. When something breaks on one site at 2 AM, separate logs save you from grepping through one giant shared log.

Create the second site's file the same way, swapping in site2.example.com:

sudo nano /etc/apache2/sites-available/site2.example.com.conf
<VirtualHost *:80>
    ServerName site2.example.com
    ServerAlias www.site2.example.com
    DocumentRoot /var/www/site2.example.com/public_html

    ErrorLog ${APACHE_LOG_DIR}/site2.example.com-error.log
    CustomLog ${APACHE_LOG_DIR}/site2.example.com-access.log combined
</VirtualHost>

Step 4: Enable the Sites and Disable the Default

Files in sites-available are inert until enabled. Ubuntu's a2ensite helper creates a symlink into sites-enabled:

sudo a2ensite site1.example.com.conf
sudo a2ensite site2.example.com.conf
sudo a2dissite 000-default.conf

Disabling 000-default.conf matters: it is the catch-all that serves the Apache welcome page for any request that matches no virtual host. If you leave it enabled, mistyped domains (and bots scanning your IP) land on the default page instead of your sites.

Step 5: Test the Configuration and Reload Apache

Always validate the config before reloading — a syntax error on reload would take all your sites down, not just the new one:

sudo apache2ctl configtest

You want to see Syntax OK. Only then reload:

sudo systemctl reload apache2

Use reload, not restart, whenever you change config. Reload re-reads the configuration without dropping active connections, so visitors experience zero downtime.

Step 6: Open the Firewall

Ubuntu's UFW ships with application profiles for Apache. Allow HTTP and HTTPS traffic:

sudo ufw allow 'Apache Full'
sudo ufw status

The Apache Full profile opens ports 80 and 443. (Opening 443 now saves you a step later when you add HTTPS — our companion guide covers Let's Encrypt on Ubuntu.) Skip this step only if you manage firewall rules elsewhere, like a cloud security group.

Step 7: Point Your Domains at the Server

In your DNS provider's panel, create A records for each domain and its www subdomain, all pointing to your server's public IP:

site1.example.com      A   203.0.113.10
www.site1.example.com  A   203.0.113.10
site2.example.com      A   203.0.113.10
www.site2.example.com  A   203.0.113.10

DNS propagation can take minutes to hours. To test before it propagates, add temporary entries to the hosts file on your own laptop (not the server):

203.0.113.10  site1.example.com www.site1.example.com
203.0.113.10  site2.example.com www.site2.example.com

On Windows this file is C:\Windows\System32\drivers\etc\hosts; on macOS and Linux it is /etc/hosts. Your browser will then resolve the domains straight to your server, bypassing DNS entirely. Remove these lines once real DNS is live.

Verify It Worked

Run these checks in order:

  1. Virtual host map: sudo apache2ctl -S prints every virtual host Apache loaded and which IP:port each listens on. Both your domains should appear under *:80. This is the single best debugging command for virtual hosts.
  2. Per-site response before DNS: curl -H "Host: site1.example.com" http://203.0.113.10/ should return "Site 1 is working", and the same with site2.example.com should return "Site 2 is working". The -H "Host: ..." flag fakes the header Apache uses to pick the virtual host, so this works even with no DNS.
  3. Browser test: visit http://site1.example.com and http://site2.example.com — each shows its own page.
  4. Logs: sudo tail -f /var/log/apache2/site1.example.com-access.log while loading the site to watch requests land in the right per-site log.

Common Issues and Fixes

1. 403 Forbidden on a new site

The most common virtual-host error. It almost always means filesystem permissions: the www-data user cannot read the DocumentRoot. Fix ownership and permissions, then reload:

sudo chown -R www-data:www-data /var/www/site1.example.com/public_html
sudo chmod -R 755 /var/www/site1.example.com
sudo systemctl reload apache2

If permissions are fine, check you did not add a restrictive <Directory> block — Apache 2.4 requires Require all granted inside any custom directory block.

2. The Apache default page shows instead of your site

Either you forgot a2ensite + reload, or 000-default.conf is still enabled and answering first. Run sudo apache2ctl -S — if your domain is missing from the list, the site was never enabled. If it is there but the default page still shows, disable the default with sudo a2dissite 000-default.conf and reload.

3. "Could not reliably determine the server's fully qualified domain name"

This warning on reload is harmless, but it clutters logs. Silence it by setting a global ServerName:

echo "ServerName localhost" | sudo tee /etc/apache2/conf-available/servername.conf
sudo a2enconf servername
sudo systemctl reload apache2

4. configtest reports a syntax error after editing

Usually an unclosed <VirtualHost> tag or a typo in a directive name. apache2ctl configtest prints the file and line number — fix it there, re-run until you see Syntax OK, and never reload on a failing test.

5. Changes do not appear in the browser

You edited the file in sites-available but forgot to reload, or your browser cached the old page. Reload Apache with sudo systemctl reload apache2 and hard-refresh with Ctrl+Shift+R. Also confirm you edited the .conf in sites-available — never edit the symlinks in sites-enabled directly.

Where to Go From Here

You now have true multi-site hosting: add a third or fourth site by repeating Steps 2–5, each with its own domain, files, and logs. The natural next step is HTTPS — grab free certificates with Certbot (sudo certbot --apache -d site1.example.com -d www.site1.example.com), which creates a separate *:443 virtual host per domain automatically.

Setting up Apache virtual hosts is routine work, but getting permissions, DNS, and SSL right across many client sites takes experience. If you would rather hand it off, XpertBees sets up and manages production Apache and Nginx servers for businesses every day — reach out and we will take it from here.