One VPS can serve many websites. Apache does this with virtual hosts: separate configuration blocks matched by the domain name in each request (the Host header, or SNI for HTTPS). This guide covers name-based virtual hosts on Ubuntu/Debian, with notes for RHEL-family systems.
If Apache isn’t installed yet, start with installing Apache on Ubuntu.
Point the domains at the server
Create DNS A records for example.com and www.example.com (and for every other site) pointing to your server’s IP. See DNS records explained.
Create a document root per site
sudo mkdir -p /var/www/example.com/public
sudo mkdir -p /var/www/blog.example.net/public
sudo chown -R $USER:www-data /var/www/example.com /var/www/blog.example.net
echo '<h1>example.com works</h1>' > /var/www/example.com/public/index.html
Keep each site in its own directory. A public subfolder lets you keep config and other non-public files beside the web root.
Write the virtual host file
On Debian/Ubuntu, site configs live in /etc/apache2/sites-available/:
# /etc/apache2/sites-available/example.com.conf
<VirtualHost *:80>
ServerName example.com
ServerAlias www.example.com
DocumentRoot /var/www/example.com/public
<Directory /var/www/example.com/public>
Options -Indexes +FollowSymLinks
AllowOverride All
Require all granted
</Directory>
ErrorLog ${APACHE_LOG_DIR}/example.com-error.log
CustomLog ${APACHE_LOG_DIR}/example.com-access.log combined
</VirtualHost>
- ServerName is the main domain. ServerAlias lists other names for the same site.
- AllowOverride All enables
.htaccess, which WordPress and Laravel need. If you don’t need.htaccess,Noneis faster. See .htaccess guide. -Indexesstops Apache from listing directory contents.
Enable it and reload:
sudo a2ensite example.com.conf
sudo apache2ctl configtest
sudo systemctl reload apache2
a2ensite creates a symlink in sites-enabled/. a2dissite removes it.
RHEL, Rocky, AlmaLinux
There is no sites-available. Put the same block in /etc/httpd/conf.d/example.com.conf. Use /var/log/httpd/ for logs. Then run sudo apachectl configtest && sudo systemctl reload httpd. If SELinux is enforcing, give custom web roots the right label: sudo chcon -R -t httpd_sys_content_t /var/www/example.com.
The default site
If a request matches no ServerName or ServerAlias, Apache uses the first virtual host loaded for that IP and port. On Debian that is 000-default.conf. This matters because:
- Visitors to the bare IP, or to a domain you forgot to configure, see that site.
- If you disable the default site, an alphabetically early site becomes the catch-all.
A good pattern is to keep a default vhost that serves nothing useful:
<VirtualHost *:80>
ServerName default.invalid
DocumentRoot /var/www/default
<Location />
Require all denied
</Location>
</VirtualHost>
Add HTTPS
The easiest route is Certbot. It reads your vhosts, gets certificates, and creates the :443 versions:
sudo apt install certbot python3-certbot-apache
sudo certbot --apache -d example.com -d www.example.com
It writes example.com-le-ssl.conf and can add an HTTP→HTTPS redirect. Details are in Let’s Encrypt for Apache.
Redirect www to non-www (or the reverse)
Inside the *:443 vhost, or in a separate one:
<VirtualHost *:443>
ServerName www.example.com
Redirect permanent / https://example.com/
# SSL directives…
</VirtualHost>
Per-site PHP versions
With PHP-FPM, each vhost can use a different PHP version:
<FilesMatch \.php$>
SetHandler "proxy:unix:/run/php/php8.3-fpm.sock|fcgi://localhost"
</FilesMatch>
Run sudo a2enmod proxy_fcgi setenvif first. To isolate sites better, give each site its own FPM pool running as its own user. See PHP-FPM tuning.
Troubleshooting
Every domain shows the same site. The request isn’t matching your ServerName. Check the spelling, make sure the site is enabled (ls /etc/apache2/sites-enabled/), and run:
sudo apache2ctl -S
This lists every vhost, its file and line, and which one is the default for each port.
403 Forbidden. Either the <Directory> block is missing Require all granted, or Apache (www-data) can’t read the files. Every parent directory needs execute (x) permission. See Linux file permissions.
Changes don’t apply. You edited a file in sites-available that isn’t enabled, or you didn’t reload. Always run configtest before reloading.
HTTPS shows the wrong certificate. The :443 vhost for that name is missing, so Apache served the first SSL vhost. Run Certbot for that domain.
Quick reference
| Task | Debian/Ubuntu | RHEL family |
|---|---|---|
| Config location | /etc/apache2/sites-available/ |
/etc/httpd/conf.d/ |
| Enable site | a2ensite name |
(any .conf loads) |
| Test config | apache2ctl configtest |
apachectl configtest |
| List vhosts | apache2ctl -S |
httpd -S |
| Reload | systemctl reload apache2 |
systemctl reload httpd |
Prefer Nginx? The equivalent is covered in Nginx server blocks.