My First Web Deployment Struggle
When I first started working with Linux servers, I deployed a PHP project to an Ubuntu VPS and spent hours wondering why hitting the IP address showed Apache’s default page instead of my project. It took nearly half a day to figure out: installing Apache isn’t enough — you still need to configure virtual hosts, set directory permissions, and open the right ports on the firewall.
After about 6 months running Apache2 in production — including a web app for a 5-developer team — I’ve been through every kind of “stupid” error that the official docs don’t mention. This article is the workflow I actually use, not a copy-paste from the manual.
Why Does Apache2 Cause Headaches Right from the Start?
The issue isn’t that Apache is complex. Ubuntu 22.04 has a few changes compared to older versions that many tutorials haven’t caught up with:
- UFW blocks port 80/443 by default — you can install Apache and the firewall will still block all traffic.
- The
/etc/apache2/directory structure uses asites-available/sites-enabledsystem — completely different from what many older guides describe. - PHP-FPM vs mod_php — since PHP 7.4, Ubuntu recommends PHP-FPM but it requires additional configuration and isn’t as plug-and-play as before.
Understanding these 3 points explains 80% of why Apache “works but doesn’t work” on your machine.
Installing Apache2
Update the package list and install:
sudo apt update
sudo apt install apache2 -y
Check if Apache is running:
sudo systemctl status apache2
If you see active (running), you’re good. If it shows failed, run sudo journalctl -xe to see the specific reason. Enable auto-start on reboot:
sudo systemctl enable apache2
Properly Configuring UFW Firewall
This is the most commonly skipped step. Apache comes with pre-built profiles for UFW:
# List available profiles
sudo ufw app list
# Allow HTTP and HTTPS
sudo ufw allow 'Apache Full'
# If you only need HTTP for now
sudo ufw allow 'Apache'
# Enable UFW if not already enabled
sudo ufw enable
sudo ufw status
After this step, typing your server’s IP address into a browser should show the “Apache2 Ubuntu Default Page” — confirming everything is running correctly.
Configuring Virtual Hosts — The Ubuntu Way
I’ve seen many people edit /etc/apache2/apache2.conf directly — this is the wrong approach. On Ubuntu, each website should have its own configuration file in /etc/apache2/sites-available/.
Create the website directory
sudo mkdir -p /var/www/mywebsite.com/public
sudo chown -R $USER:$USER /var/www/mywebsite.com
sudo chmod -R 755 /var/www/mywebsite.com
Create the virtual host configuration file
sudo nano /etc/apache2/sites-available/mywebsite.com.conf
File contents:
<VirtualHost *:80>
ServerName mywebsite.com
ServerAlias www.mywebsite.com
ServerAdmin [email protected]
DocumentRoot /var/www/mywebsite.com/public
<Directory /var/www/mywebsite.com/public>
Options -Indexes +FollowSymLinks
AllowOverride All
Require all granted
</Directory>
ErrorLog ${APACHE_LOG_DIR}/mywebsite.com-error.log
CustomLog ${APACHE_LOG_DIR}/mywebsite.com-access.log combined
</VirtualHost>
Enable the site and reload Apache
# Enable the new site
sudo a2ensite mywebsite.com.conf
# Disable the default site if not needed
sudo a2dissite 000-default.conf
# Check configuration syntax
sudo apache2ctl configtest
# Reload Apache
sudo systemctl reload apache2
The apache2ctl configtest command is something I always run before reloading — it prevents Apache from crashing due to a syntax error in the config.
Enabling Required Modules
Apache2 on Ubuntu uses the a2enmod command to enable modules. Some commonly used ones:
# Required if using .htaccess with WordPress/Laravel
sudo a2enmod rewrite
# SSL
sudo a2enmod ssl
# Gzip compression for static files
sudo a2enmod deflate
# Security headers
sudo a2enmod headers
sudo systemctl restart apache2
For the 5-developer team project, I rolled these 4 commands into a server bootstrap script. Run it once and you’re done — no more debugging sessions caused by module mismatches between dev and production.
Setting Up SSL with Let’s Encrypt
Once your domain is pointed at the server, Certbot is the fastest option:
sudo apt install certbot python3-certbot-apache -y
sudo certbot --apache -d mywebsite.com -d www.mywebsite.com
Certbot automatically modifies your .conf file, adds SSL configuration for port 443, and sets up HTTP-to-HTTPS redirect. Certificates expire after 90 days, but auto-renewal is already configured via a systemd timer:
# Check the auto-renewal timer
sudo systemctl status certbot.timer
# Test renewal
sudo certbot renew --dry-run
Managing Multiple Websites on a Single VPS
After running multiple projects on the same VPS, I’ve settled on this structure:
/var/www/
├── site1.com/
│ └── public/ # DocumentRoot
├── site2.com/
│ └── public/
└── site3.com/
└── public/
/etc/apache2/
├── sites-available/
│ ├── site1.com.conf
│ ├── site2.com.conf
│ └── site3.com.conf
└── sites-enabled/ # Only contains symlinks, never create files here
The rule: one conf file per domain, use a2ensite/a2dissite to toggle — never edit sites-enabled directly.
Reading Logs When Something Goes Wrong
9 times out of 10, the answer to any Apache error is right there in the error log:
# Watch errors in real-time
sudo tail -f /var/log/apache2/error.log
# View logs by domain (if custom log is configured)
sudo tail -f /var/log/apache2/mywebsite.com-error.log
# View access log
sudo tail -f /var/log/apache2/mywebsite.com-access.log
Getting a 403 Forbidden? It’s usually incorrect directory permissions or a missing AllowOverride All in the config. Getting a 500? The error log will point to the exact problem line — no guesswork needed.
Standard Setup Checklist
- Install Apache2 → enable the service
- Open the firewall with UFW (
Apache Full) - Create the website directory with correct permissions
- Create a
.conffile insites-available - Enable the site with
a2ensite, test the config, reload - Enable required modules (
rewrite,ssl, …) - Install SSL with Certbot
Apache2 is honestly not as intimidating as people think. Most cases of a server being “unreachable” come down to UFW not opening the port or the virtual host not being enabled. Check those two things first — then start looking for more complex causes.

