Guides

How to install Apache on Ubuntu and Debian: virtual hosts, PHP-FPM and HTTPS

Install Apache with apt install apache2 on Ubuntu or Debian: open ufw, add a virtual host, run PHP-FPM, get HTTPS with Certbot. Plus Rocky's httpd.

Published ~9 min read
On this page

Short answer: run sudo apt install apache2 on Ubuntu or Debian. Apache starts right away and serves its default page on port 80. Open HTTP and HTTPS in ufw, put each site in /etc/apache2/sites-available, enable it with a2ensite, check with apache2ctl configtest and reload. For HTTPS, run certbot --apache. On Rocky Linux and AlmaLinux the package is httpd.

Ubuntu · Debian
sudo apt update
sudo apt install -y apache2
systemctl status apache2 --no-pager
apache2ctl -v

The paths, helper scripts and messages below follow Debian’s apache2 packaging (version 2.4.68-1~deb12u1 in the Debian 12 package index on our server), Ubuntu’s packaging and the documentation on httpd.apache.org, read on 21 October 2026. The Debian ufw profile names (WWW Full, OpenSSH) were checked in /etc/ufw/applications.d on Debian 12 with ufw 0.36.2; the Ubuntu ones (Apache, Apache Secure, Apache Full) come from Ubuntu’s apache2 packaging. Apache itself was not run for this guide, so outputs are marked as examples. The newest upstream release is 2.4.69 (1 October 2026); distributions keep their own 2.4.x and backport security fixes, so apache2ctl -v shows a lower number on a fully patched server.

Open ports 80 and 443 in ufw

Open ports 80 and 443: 443 for HTTPS, 80 for redirects and for Let’s Encrypt’s HTTP check. Ubuntu’s apache2 package adds three ufw profiles; Debian’s does not, but ufw itself ships a profile with the same ports:

ufw
sudo ufw allow OpenSSH          # first, so you keep your SSH session
sudo ufw allow "Apache Full"    # Ubuntu only: 80 and 443
sudo ufw allow "WWW Full"       # Debian only: the same ports, from ufw's own profile
sudo ufw enable
sudo ufw app list
ProfilePortsComes with
Apache80/tcpUbuntu's apache2
Apache Secure443/tcpUbuntu's apache2
Apache Full80,443/tcpUbuntu's apache2
WWW Full80,443/tcpDebian's ufw package (Ubuntu's ufw does not install it)

Then open http://YOUR-SERVER-IP/. The “Apache2 Debian Default Page” (or the Ubuntu one) is /var/www/html/index.html. More rules, such as limiting SSH to your own address, are in the ufw guide.

The Debian and Ubuntu layout

Debian splits Apache’s configuration into small files and gives you scripts that switch them on and off with symlinks, so you rarely edit the main file:

PathWhat it holds
/etc/apache2/apache2.confMain configuration: global settings, directory permissions, includes
/etc/apache2/ports.confThe Listen lines (80, and 443 when mod_ssl is on)
sites-available/ · sites-enabled/One file per site; enabled ones are symlinks. 000-default.conf is the default site
mods-available/ · mods-enabled/Module load lines and their settings
conf-available/ · conf-enabled/Extra snippets such as security.conf or the PHP-FPM handler
/etc/apache2/envvarsRun user (www-data) and APACHE_LOG_DIR, read by apache2ctl, which the systemd unit calls
/var/log/apache2/access.log, error.log, other_vhosts_access.log
TaskCommand
Enable or disable a sitea2ensite example.com · a2dissite 000-default
Enable or disable a modulea2enmod rewrite · a2dismod autoindex
Enable or disable a config snippeta2enconf servername · a2disconf serve-cgi-bin
Check the syntaxsudo apache2ctl configtest
List virtual hosts and which file defines themsudo apache2ctl -S
List loaded modulessudo apache2ctl -M
Apply site or config changessudo systemctl reload apache2
Apply module changessudo systemctl restart apache2
Example output
$ sudo a2enmod rewrite
Enabling module rewrite.
To activate the new configuration, you need to run:
  systemctl restart apache2

The scripts suggest a restart for modules and a reload for sites and config snippets. Run apache2ctl configtest before either: a reload with a broken file keeps the old configuration running, but a restart with one leaves Apache stopped. The full list of service commands is in systemctl commands.

Set up a virtual host for your domain

  1. 1Point the domain at the server

    Create A (and AAAA) records for example.com and www.example.com with your server’s address, as described in pointing a domain to a VPS. You can build the site while DNS propagates.

  2. 2Create the document root
    shell
    sudo mkdir -p /var/www/example.com/public
    echo '<h1>example.com works</h1>' | sudo tee /var/www/example.com/public/index.html
  3. 3Write the site file
    /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>
  4. 4Enable it, test, reload
    shell
    sudo a2ensite example.com
    sudo a2dissite 000-default
    sudo apache2ctl configtest
    sudo systemctl reload apache2
    curl -H "Host: example.com" http://127.0.0.1/

    configtest should end with Syntax OK. The curl line asks for the site by name before DNS has caught up.

AllowOverride All is there for .htaccess files. Debian’s apache2.conf sets AllowOverride None for /var/www/, so without this line every .htaccess rewrite is silently ignored; with it, also run sudo a2enmod rewrite. The Apache documentation recommends putting rules into the site file instead where you can, because with overrides allowed Apache looks for .htaccess in every directory on every request. Debian also only grants access to /var/www and /usr/share; a document root under /srv needs its own <Directory> block with Require all granted, or visitors get a 403 Forbidden.

The AH00558 ServerName warning

Example output
AH00558: apache2: Could not reliably determine the server's fully qualified domain name, using 127.0.1.1. Set the 'ServerName' directive globally to suppress this message

Harmless, but easy to silence with a global ServerName, ideally the server’s fully qualified domain name:

shell
echo "ServerName server1.example.com" | sudo tee /etc/apache2/conf-available/servername.conf
sudo a2enconf servername
sudo systemctl reload apache2

PHP: PHP-FPM with proxy_fcgi, or mod_php

Debian and Ubuntu run Apache with the event MPM, which handles many idle keep-alive connections with few threads. PHP-FPM keeps it that way:

Debian 12 (PHP 8.2)
sudo apt install -y php-fpm
sudo a2enmod proxy_fcgi setenvif
sudo a2enconf php8.2-fpm
sudo systemctl restart apache2
Example output while installing php-fpm
NOTICE: Not enabling PHP 8.2 FPM by default.
NOTICE: To enable PHP 8.2 FPM in Apache2 do:
NOTICE: a2enmod proxy_fcgi setenvif
NOTICE: a2enconf php8.2-fpm
NOTICE: You are seeing this message because you have apache2 package installed.

Use the version your release ships: php8.3-fpm on Ubuntu 24.04, php8.4-fpm on Debian 13; ls /etc/apache2/conf-available/ | grep fpm shows the name. The snippet hands every .php file to the FPM socket in /run/php/. To test, put <?php phpinfo(); into a file in the document root, look for “Server API: FPM/FastCGI”, then delete the file. PHP settings now live in /etc/php/8.2/fpm/php.ini, not the Apache one, which is where most memory_limit changes go wrong.

PHP-FPM + proxy_fcgimod_php (libapache2-mod-php)
MPMEvent, the defaultSwitches Apache to prefork, because mod_php is not thread-safe
MemoryPHP only in its own pool of workersA PHP interpreter in every Apache process, also for static files
Run each site as its own userYes, one pool per siteNo, everything runs as www-data
SetupThree commandsOne package
Use it forNew serversOld applications that rely on Apache-only PHP features

With PHP in place, the database half of a LAMP stack is covered in installing MariaDB or MySQL.

HTTPS with Certbot

Ubuntu · Debian
sudo apt install -y certbot python3-certbot-apache
sudo certbot --apache -d example.com -d www.example.com
sudo certbot renew --dry-run

Certbot proves control of the domain over port 80, so DNS must already point at the server and the firewall must let port 80 in. It then writes a second site file, example.com-le-ssl.conf, enables mod_ssl and that site, and by default adds a redirect from HTTP to HTTPS. The Debian and Ubuntu package renews through a systemd timer (systemctl list-timers | grep certbot), and the dry run proves that renewal will work.

Logs and troubleshooting

shell
sudo tail -f /var/log/apache2/error.log
sudo tail -f /var/log/apache2/example.com-access.log
sudo journalctl -u apache2 -n 50 --no-pager

The error log names the file and the cause for most problems; when Apache does not start at all, journalctl has the reason. tail -f keeps the log open while you reload the page. Sites without their own CustomLog write to other_vhosts_access.log.

Message or symptomCauseFix
(98)Address already in use: AH00072: make_sock: could not bind to address [::]:80nginx or another program holds port 80Find it with sudo ss -tlnp | grep :80; see address already in use
AH00526: Syntax error on line 9 of …: Invalid command 'RewriteEngine'The module behind the directive is offsudo a2enmod rewrite
AH01630: client denied by server configurationNo Require all granted for that directoryAdd a Directory block, see above
.htaccess rules have no effectAllowOverride None, or mod_rewrite offAllowOverride All in the site, a2enmod rewrite
The browser downloads .php files or shows PHP codeNo PHP handler enableda2enconf php8.2-fpm
AH02454: FCGI: attempt to connect to Unix domain socket … failedPHP-FPM is not running or the socket name differsStart PHP-FPM; a 503 follows until then

Rocky Linux and AlmaLinux: httpd

Rocky Linux 9 · AlmaLinux 9
sudo dnf install -y httpd
sudo systemctl enable --now httpd
sudo firewall-cmd --permanent --add-service=http --add-service=https
sudo firewall-cmd --reload
sudo apachectl configtest
Debian · UbuntuRocky · AlmaLinux
Package and serviceapache2httpd
Started after installYesNo: systemctl enable --now httpd
Main file/etc/apache2/apache2.conf/etc/httpd/conf/httpd.conf
Sitessites-available + a2ensiteAny .conf file in /etc/httpd/conf.d/
Modulesa2enmod / a2dismodLoadModule lines in /etc/httpd/conf.modules.d/
TLS moduleIncluded: a2enmod sslSeparate package: dnf install mod_ssl
Logs/var/log/apache2/access.log/var/log/httpd/access_log
Run userwww-dataapache
Firewallufwfirewalld
Extra layerNoneSELinux

SELinux is the usual surprise. A document root outside /var/www needs the right label, and Apache may only open outgoing connections, for example to a backend it proxies to, once a boolean allows it:

Rocky Linux · AlmaLinux
sudo semanage fcontext -a -t httpd_sys_content_t "/srv/www(/.*)?"
sudo restorecon -Rv /srv/www
sudo setsebool -P httpd_can_network_connect 1

Apache or nginx?

Apache httpdnginx
.htaccess filesSupported; many PHP apps ship themNot supported; rules go into the server config
PHPPHP-FPM or mod_phpPHP-FPM only
ConfigurationDirectives per directory, many modulesOne server block per site, compact
Reverse proxy and TLS front endCapable (mod_proxy)The common choice
Certbot plugin--apache--nginx
Pick it whenAn app expects .htaccess, or you know ApacheYou serve static files, proxy to apps, or start fresh

Both are fast enough for nearly any site on one server. How the two differ under the hood is in what nginx is and how it works.

Running both: Apache behind nginx

Only one program can own port 80. A common split is nginx in front for TLS and static files and Apache behind it for the PHP sites whose .htaccess rules nobody wants to translate:

  1. 1Move Apache to localhost:8080
    /etc/apache2/ports.conf and the site file
    # ports.conf: replace "Listen 80"
    Listen 127.0.0.1:8080
    
    # sites-available/example.com.conf
    <VirtualHost 127.0.0.1:8080>
        ...
        SetEnvIf X-Forwarded-Proto https HTTPS=on
    </VirtualHost>

    The SetEnvIf line tells PHP applications that the visitor used HTTPS, which prevents redirect loops in WordPress and similar apps. Disable mod_ssl and the -le-ssl site if Certbot had set them up, since nginx now handles TLS.

  2. 2Log the real visitor address
    /etc/apache2/conf-available/remoteip.conf
    RemoteIPHeader X-Forwarded-For
    RemoteIPInternalProxy 127.0.0.1

    Run sudo a2enmod remoteip and sudo a2enconf remoteip, and change %h to %a in the LogFormat lines of apache2.conf, so the log shows the visitor instead of 127.0.0.1.

  3. 3Proxy from nginx
    /etc/nginx/sites-available/example.com
    server {
        listen 80;
        server_name example.com www.example.com;
    
        location / {
            proxy_pass http://127.0.0.1:8080;
            proxy_set_header Host $host;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }
    }

    Restart Apache first, then reload nginx. TLS on the nginx side works as in nginx as a reverse proxy with SSL.

Apache on a VPS

  • Memory: Apache with PHP-FPM, MariaDB and a small WordPress site fit on the smallest Standard KVM plan (2 cores, 4 GB RAM). With mod_php and prefork, every Apache process carries PHP, so the same traffic needs more RAM.
  • Images: our Ubuntu and Debian VPS images take the apt commands on this page as written, with full root access and always-on DDoS protection in front.
  • No server to run: if you only need websites, our Plesk hosting includes free auto-renewing SSL and the WordPress Toolkit, and the web server is managed for you.

Frequently asked

How do I restart Apache on Ubuntu or Debian?

Run 'sudo systemctl restart apache2'. For changes to sites or config snippets 'sudo systemctl reload apache2' is enough and keeps open connections. Run 'sudo apache2ctl configtest' first: it should print 'Syntax OK', because a restart with a broken file leaves Apache stopped. On Rocky Linux and AlmaLinux the service is httpd.

Where is the Apache configuration file on Ubuntu?

The main file is /etc/apache2/apache2.conf, the Listen ports are in /etc/apache2/ports.conf, and each site has its own file in /etc/apache2/sites-available that a2ensite enables. Logs are in /var/log/apache2. On Rocky Linux and AlmaLinux the main file is /etc/httpd/conf/httpd.conf and sites go into /etc/httpd/conf.d.

Can Apache and nginx run on the same server?

Yes, but not on the same port. The usual setup moves Apache to 127.0.0.1:8080 in ports.conf and the site file, and lets nginx answer on ports 80 and 443 and pass requests to Apache with proxy_pass. Enable mod_remoteip so Apache logs the visitor's address instead of 127.0.0.1.

How do I check which Apache version is installed?

Run 'apache2ctl -v' on Ubuntu and Debian, or 'httpd -v' on Rocky Linux and AlmaLinux. Distributions keep their own 2.4.x release and backport security fixes, so the number is often lower than the newest upstream release (2.4.69 as of October 2026) even on a fully updated server.

Related articles

All guides