Quick Start — Get Apache Running in 5 Minutes
Just need Apache installed and serving a page? Here’s the shortest path. I’ll assume you’re on Ubuntu 22.04 — same setup I run in production.
# Install Apache
sudo apt update
sudo apt install apache2 -y
# Check if it's running
sudo systemctl status apache2
# Open firewall for HTTP and HTTPS
sudo ufw allow 'Apache Full'
Open your browser and go to http://your-server-ip. You should see the Apache2 Ubuntu Default Page — Apache is up.
The default web root is /var/www/html/. Drop an index.html file there and it shows up immediately. That’s the basics.
Now, the real question: what if you want to host two or more different websites on the same server? That’s where virtual hosts come in.
Deep Dive — Understanding Virtual Hosts
What Is a Virtual Host?
A virtual host tells Apache: “When a request comes in for domain A, serve files from this folder. When it’s domain B, serve from that other folder.”
Apache supports two types:
- Name-based virtual hosts — multiple domains share the same IP address (most common)
- IP-based virtual hosts — each domain gets its own IP (rare, mostly for legacy SSL setups)
We’re using name-based virtual hosts throughout this guide. It’s what 99% of setups use.
Apache’s Config File Layout
Before touching anything, understand how Apache organizes its config files on Ubuntu:
/etc/apache2/
├── apache2.conf # Main config
├── ports.conf # Which ports Apache listens on
├── sites-available/ # All virtual host configs (enabled or not)
│ ├── 000-default.conf # Default site config
│ └── default-ssl.conf
├── sites-enabled/ # Symlinks to active sites
├── mods-available/ # Available Apache modules
└── mods-enabled/ # Symlinks to active modules
Here’s the key idea: write your config in sites-available/, then enable it with a2ensite. That command creates a symlink in sites-enabled/. Apache only reads what’s symlinked there — nothing else.
Setting Up Your First Virtual Host
Let’s say you’re hosting two sites: site1.com and site2.com.
Step 1 — Create web root directories:
sudo mkdir -p /var/www/site1.com/public_html
sudo mkdir -p /var/www/site2.com/public_html
# Set correct ownership
sudo chown -R $USER:$USER /var/www/site1.com
sudo chown -R $USER:$USER /var/www/site2.com
# Set permissions
sudo chmod -R 755 /var/www/site1.com
sudo chmod -R 755 /var/www/site2.com
Step 2 — Add a test page for each site:
echo '<h1>Welcome to Site 1</h1>' | sudo tee /var/www/site1.com/public_html/index.html
echo '<h1>Welcome to Site 2</h1>' | sudo tee /var/www/site2.com/public_html/index.html
Step 3 — Create virtual host config files:
sudo nano /etc/apache2/sites-available/site1.com.conf
Paste this in:
<VirtualHost *:80>
ServerName site1.com
ServerAlias www.site1.com
DocumentRoot /var/www/site1.com/public_html
ErrorLog ${APACHE_LOG_DIR}/site1-error.log
CustomLog ${APACHE_LOG_DIR}/site1-access.log combined
<Directory /var/www/site1.com/public_html>
Options Indexes FollowSymLinks
AllowOverride All
Require all granted
</Directory>
</VirtualHost>
For site2, copy the file and do a bulk replace:
sudo cp /etc/apache2/sites-available/site1.com.conf /etc/apache2/sites-available/site2.com.conf
sudo sed -i 's/site1/site2/g' /etc/apache2/sites-available/site2.com.conf
Step 4 — Enable the sites and reload Apache:
sudo a2ensite site1.com.conf
sudo a2ensite site2.com.conf
# Optionally disable the default site
sudo a2dissite 000-default.conf
# Test your config before reloading — always do this
sudo apache2ctl configtest
# Reload Apache
sudo systemctl reload apache2
If configtest prints Syntax OK, you’re good. Never skip this step — it has saved me from more than a few 3am headaches.
Advanced Usage — HTTPS, .htaccess, and Multiple Ports
Enabling HTTPS with Let’s Encrypt (Certbot)
HTTP works fine for local testing. For production, you need HTTPS — and Certbot is the cleanest way to get there:
sudo apt install certbot python3-certbot-apache -y
sudo certbot --apache -d site1.com -d www.site1.com
Certbot asks for your email, you agree to the terms, and it automatically modifies your Apache config to add SSL. Auto-renewal runs via a systemd timer. Certificate expiration becomes someone else’s problem.
Verify renewal works before you forget about it:
sudo certbot renew --dry-run
Using .htaccess for Per-Directory Rules
AllowOverride All in the virtual host config is what enables .htaccess files. With it, you can set rewrite rules, redirects, and access controls per-folder — without touching the main Apache config.
Redirecting HTTP to HTTPS and stripping the www prefix are the two most common use cases:
# /var/www/site1.com/public_html/.htaccess
RewriteEngine On
# Redirect www to non-www
RewriteCond %{HTTP_HOST} ^www\.site1\.com [NC]
RewriteRule ^ https://site1.com%{REQUEST_URI} [L,R=301]
# Redirect HTTP to HTTPS
RewriteCond %{HTTPS} off
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
Make sure mod_rewrite is enabled:
sudo a2enmod rewrite
sudo systemctl reload apache2
Running a Virtual Host on a Non-Standard Port
Port 80 taken by another service? Add port 8080 to ports.conf first:
echo 'Listen 8080' | sudo tee -a /etc/apache2/ports.conf
Then change *:80 to *:8080 in your virtual host config:
<VirtualHost *:8080>
ServerName site1.com
DocumentRoot /var/www/site1.com/public_html
...
</VirtualHost>
Practical Tips from the Field
Separate Log Files Per Virtual Host
Always define separate ErrorLog and CustomLog for each virtual host. When site2 breaks at 2am, you want to tail its log directly — not sift through a single file mixing output from five different sites:
sudo tail -f /var/log/apache2/site2-error.log
Check Which Config Apache Is Actually Using
# List all enabled sites
ls -la /etc/apache2/sites-enabled/
# Show full compiled config
sudo apache2ctl -S
The -S flag is the one I reach for first when something misbehaves. It shows every active virtual host, the port it binds to, and its document root. No guessing.
Performance Note from My Own Setup
On my Ubuntu 22.04 server with 4GB RAM, I switched from prefork MPM to event MPM after enabling PHP-FPM. The difference was noticeable: prefork was allocating ~35MB per worker process, capping out around 60–70 concurrent requests before memory ran out. After switching to event MPM + PHP-FPM, the same box handled 200+ concurrent connections comfortably — Apache itself became nearly stateless, and PHP-FPM managed its own worker pool separately.
# Switch from prefork to event MPM
sudo a2dismod mpm_prefork
sudo a2enmod mpm_event
sudo systemctl restart apache2
If you’re running PHP, pair this with PHP-FPM and the proxy_fcgi module. More concurrent visitors, same hardware.
Quick Debug Checklist
When a virtual host isn’t working, run through this list in order:
- Run
sudo apache2ctl configtest— a syntax error stops everything - Check
sudo systemctl status apache2— is it actually running? - Verify DNS points to your server IP (or test locally by editing
/etc/hosts) - Check file permissions on the document root — Apache needs read access
- Read the site-specific error log, not the generic one
- Confirm the required module is enabled (
ls /etc/apache2/mods-enabled/)
Apache virtual hosts look complicated on paper. Set them up once and the whole system clicks. The symlink pattern between sites-available and sites-enabled, the a2ensite/a2dissite shortcuts — it scales cleanly whether you’re running 2 sites or 20 on the same box.

