Web Server

Raspberry Pi Projects

Project 1 — Setting up a Raspberry Pi as a Web Server

What you will build A full LAMP web server (Apache + PHP + MariaDB) with SSL, accessible from the internet
Difficulty Beginner — Intermediate
Time to complete 1 – 2 hours
Recommended hardware Raspberry Pi 3B+ or Pi 4 (2 GB RAM or more)

What You Will Need

🍓 Raspberry Pi Pi 3B+, Pi 4, or Pi 5. Pi Zero 2W works but will be slow under load.
💾 Raspberry Pi OS Raspberry Pi OS Lite (64-bit) is ideal — no desktop needed for a server.
🌐 Internet connection Wired Ethernet recommended for a server. Wi-Fi works but is less reliable.
🔒 A domain name Required for SSL certificates. Free options: DuckDNS, No-IP. Paid: Namecheap, GoDaddy.
📡 Router access You will need to set up port forwarding (ports 80 and 443) on your router.
💻 SSH access All steps are done via the terminal. Enable SSH when flashing with Raspberry Pi Imager.
First time with Raspberry Pi OS? Use the Raspberry Pi Imager to flash the OS to your SD card. In the settings (gear icon), set your hostname, enable SSH, and set a username/password before you flash — this saves a lot of time.

Step 1 — Update Your Pi

Always start with a full system update. This ensures you are installing the latest versions of all packages and avoids dependency conflicts later.

pi@raspberrypi:~$ sudo apt update && sudo apt upgrade -y
Hit:1 http://archive.raspberrypi.com/debian bookworm InRelease
Get:2 http://deb.debian.org/debian bookworm InRelease [151 kB]
...
Reading package lists... Done
Building dependency tree... Done

This may take a few minutes. Once complete, reboot to ensure all updates take effect:

pi@raspberrypi:~$ sudo reboot

Wait about 30 seconds then SSH back in before continuing.

Step 2 — Install Apache

Apache is the world's most widely used web server. It handles incoming HTTP/HTTPS requests and serves your web pages.

pi@raspberrypi:~$ sudo apt install -y apache2
Reading package lists... Done
...
Setting up apache2 (2.4.57-2+deb12u2) ...
Enabling module mpm_event. ...

Verify Apache is running

pi@raspberrypi:~$ sudo systemctl status apache2
● apache2.service - The Apache HTTP Server
     Loaded: loaded (/lib/systemd/system/apache2.service; enabled)
     Active: active (running) since Wed 2026-06-11 09:14:33 BST; 22s ago

Now test it from a browser on the same network. Find your Pi's local IP address:

pi@raspberrypi:~$ hostname -I
192.168.1.42

Open a browser and visit http://192.168.1.42. You should see the Apache default page — "It works!"

Enable Apache to start on boot

pi@raspberrypi:~$ sudo systemctl enable apache2
Synchronizing state of apache2.service...

Step 3 — Install PHP

PHP is the server-side scripting language used by WordPress, phpMyAdmin, and most dynamic websites.

pi@raspberrypi:~$ sudo apt install -y php libapache2-mod-php php-mysql php-curl php-gd php-mbstring php-xml php-zip
Reading package lists... Done
...
Setting up php8.2 (8.2.20-1~deb12u1) ...

Verify PHP is working

Create a test PHP file in Apache's web root:

pi@raspberrypi:~$ echo '<?php phpinfo(); ?>' | sudo tee /var/www/html/info.php

Visit http://192.168.1.42/info.php in your browser. You should see the PHP information page showing your PHP version and configuration.

Security note: Remove the test file once you have confirmed PHP is working — it exposes configuration details about your server.

sudo rm /var/www/html/info.php

Step 4 — Install MariaDB

MariaDB is a fast, open-source relational database (a drop-in replacement for MySQL) used by WordPress and most web applications.

pi@raspberrypi:~$ sudo apt install -y mariadb-server
Reading package lists... Done
...
Setting up mariadb-server (1:10.11.6-0+deb12u1) ...

Secure the installation

Run the security script to set a root password, remove test databases, and disable remote root login:

pi@raspberrypi:~$ sudo mysql_secure_installation

Enter current password for root (enter for none): [press Enter]
Switch to unix_socket authentication [Y/n]: n
Change the root password? [Y/n]: Y
New password: [enter a strong password]
Re-enter new password: [repeat password]
Remove anonymous users? [Y/n]: Y
Disallow root login remotely? [Y/n]: Y
Remove test database and access to it? [Y/n]: Y
Reload privilege tables now? [Y/n]: Y
All done!

Create a database and user for your website

Rather than using the root account for your website, create a dedicated database user:

pi@raspberrypi:~$ sudo mysql -u root -p
MariaDB [(none)]> CREATE DATABASE mywebsite_db;
MariaDB [(none)]> CREATE USER 'webuser'@'localhost' IDENTIFIED BY 'StrongPassword123!';
MariaDB [(none)]> GRANT ALL PRIVILEGES ON mywebsite_db.* TO 'webuser'@'localhost';
MariaDB [(none)]> FLUSH PRIVILEGES;
MariaDB [(none)]> EXIT;

Step 5 — Configure a Virtual Host

A virtual host tells Apache which folder to serve for a given domain name. This allows you to host multiple websites on the same Pi.

Create the website directory

pi@raspberrypi:~$ sudo mkdir -p /var/www/mywebsite
pi@raspberrypi:~$ sudo chown -R $USER:$USER /var/www/mywebsite
pi@raspberrypi:~$ sudo chmod -R 755 /var/www/mywebsite

Create a test index page

pi@raspberrypi:~$ nano /var/www/mywebsite/index.html

Paste in a simple page:

<html>
  <head><title>My Pi Web Server</title></head>
  <body>
    <h1>Hello from my Raspberry Pi! 🍓</h1>
  </body>
</html>

Create the virtual host config file

pi@raspberrypi:~$ sudo nano /etc/apache2/sites-available/mywebsite.conf
<VirtualHost *:80>
    ServerName    yourdomain.com
    ServerAlias   www.yourdomain.com
    DocumentRoot  /var/www/mywebsite
    ErrorLog      ${APACHE_LOG_DIR}/mywebsite-error.log
    CustomLog     ${APACHE_LOG_DIR}/mywebsite-access.log combined

    <Directory /var/www/mywebsite>
        AllowOverride All
        Require all granted
    </Directory>
</VirtualHost>

Enable the site and reload Apache

pi@raspberrypi:~$ sudo a2ensite mywebsite.conf
pi@raspberrypi:~$ sudo a2dissite 000-default.conf     # disable the default page
pi@raspberrypi:~$ sudo a2enmod rewrite               # enable mod_rewrite (needed for WordPress etc.)
pi@raspberrypi:~$ sudo systemctl reload apache2

Step 6 — Set Up a Static Local IP

Before making your Pi accessible from the internet, give it a fixed local IP address so your port forwarding rules don't break when it reboots.

The easiest approach is to assign a DHCP reservation in your router — find your Pi's MAC address and tell the router to always give it the same IP. Look for "DHCP reservation" or "Static DHCP" in your router settings.

Alternatively, set a static IP on the Pi itself:

pi@raspberrypi:~$ sudo nano /etc/dhcpcd.conf

Add these lines at the bottom (adjust to match your network):

interface eth0
static ip_address=192.168.1.42/24
static routers=192.168.1.1
static domain_name_servers=8.8.8.8 8.8.4.4
pi@raspberrypi:~$ sudo reboot

Step 7 — Port Forwarding and Domain Setup

To make your web server reachable from the internet, you need to forward ports 80 (HTTP) and 443 (HTTPS) from your router to your Pi's local IP address.

Set up port forwarding on your router

  1. Log in to your router admin panel (usually 192.168.1.1 or 192.168.0.1)
  2. Find the Port Forwarding section (sometimes under "NAT" or "Virtual Server")
  3. Add two rules:
ServiceExternal PortInternal PortProtocolInternal IP
HTTP8080TCP192.168.1.42
HTTPS443443TCP192.168.1.42

Point your domain to your Pi

Log in to your domain registrar (or DuckDNS/No-IP) and create an A record pointing your domain to your home's public IP address. Find your public IP at api.ipify.org:

pi@raspberrypi:~$ curl https://api.ipify.org
203.0.113.42
Dynamic IP? Most home broadband connections have a changing public IP. Use DuckDNS (free) to get a hostname that automatically updates when your IP changes. The DuckDNS site has a full setup guide for Raspberry Pi.

Step 8 — Enable SSL with Let's Encrypt

Let's Encrypt provides free, automatically renewed SSL certificates. Certbot is the tool that handles the whole process.

Before running Certbot: your domain must already be pointing to your Pi's public IP and port 80 must be forwarded and working. Certbot verifies domain ownership by making an HTTP request to your server.
pi@raspberrypi:~$ sudo apt install -y certbot python3-certbot-apache
pi@raspberrypi:~$ sudo certbot --apache -d yourdomain.com -d www.yourdomain.com
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Enter email address (used for urgent renewal and security notices): your@email.com
Please read the Terms of Service at https://letsencrypt.org/documents/LE-SA-v1.4-April-3-2024.pdf
Do you agree? [Y]es/[N]o: Y
Would you like to share your email address? [Y]es/[N]o: N

Requesting a certificate for yourdomain.com and www.yourdomain.com
...
Congratulations! You have successfully enabled HTTPS on https://yourdomain.com

Certbot automatically updates your Apache virtual host config to use HTTPS and sets up a cron job to renew the certificate before it expires (certificates last 90 days). Test the auto-renewal:

pi@raspberrypi:~$ sudo certbot renew --dry-run
...
Congratulations, all simulated renewals succeeded:
  /etc/letsencrypt/live/yourdomain.com/fullchain.pem (success)

Step 9 — Final Checks

Run through this checklist to confirm everything is working correctly:

  • Visit http://yourdomain.com — does your website load?
  • Visit https://yourdomain.com — does the padlock appear in the browser?
  • Visit http://yourdomain.com — are you automatically redirected to HTTPS? (Certbot configures this by default)
  • Check Apache error logs for any warnings: sudo tail -f /var/log/apache2/mywebsite-error.log
  • Verify MariaDB is running: sudo systemctl status mariadb
  • Reboot and check that Apache and MariaDB both restart automatically

Troubleshooting Common Problems

⚠ Apache fails to start — "Address already in use"
Another service is using port 80. Find it with sudo ss -tlnp | grep :80 and stop it, or change Apache's port. This often happens if Nginx was previously installed.
⚠ "403 Forbidden" when visiting your site
The web root directory permissions are wrong. Run:
sudo chown -R www-data:www-data /var/www/mywebsite
sudo chmod -R 755 /var/www/mywebsite
Also check your virtual host config has Require all granted inside the Directory block.
⚠ Certbot fails — "Connection refused" or "Timeout"
Port 80 is not reachable from the internet. Check: (1) port forwarding is set up correctly on your router, (2) your ISP does not block port 80 (some do — use port 8080 as an alternative), (3) your domain's DNS A record is pointing to the correct public IP. DNS changes can take up to 24 hours to propagate.
⚠ PHP pages show as plain text or prompt to download
The PHP Apache module is not enabled. Run:
sudo a2enmod php8.2
sudo systemctl restart apache2
Replace php8.2 with your installed version (check with php --version).
⚠ Can't connect to MariaDB — "Access denied for user"
The password or username is incorrect. Log in as root to reset it:
sudo mysql -u root -p
ALTER USER 'webuser'@'localhost' IDENTIFIED BY 'NewPassword';
FLUSH PRIVILEGES;
⚠ Website loads locally (192.168.1.x) but not via domain name
Port forwarding or DNS is not set up correctly. Test from outside your network using a mobile data connection (not home Wi-Fi). Many routers do not support "hairpin NAT" — connecting to your own external IP from inside your home network. This is normal; test externally.
⚠ Pi runs slowly or Apache crashes under load
Switch Apache's MPM (Multi-Processing Module) from the default mpm_event to mpm_prefork for better PHP compatibility, or use PHP-FPM for better performance. Also consider adding swap space on the Pi if you have less than 2 GB RAM: sudo dphys-swapfile swapoff, edit /etc/dphys-swapfile and set CONF_SWAPSIZE=1024, then sudo dphys-swapfile setup && sudo dphys-swapfile swapon.
⚠ SSL certificate renewal fails
Check that port 80 is still forwarded (Certbot needs it for renewal even if you only serve HTTPS). View renewal logs: sudo cat /var/log/letsencrypt/letsencrypt.log. Force a renewal manually: sudo certbot renew --force-renewal.

Optional Extras

Install phpMyAdmin (database web interface)

pi@raspberrypi:~$ sudo apt install -y phpmyadmin
Web server to reconfigure automatically: apache2
Configure database for phpmyadmin with dbconfig-common? Yes

Access it at https://yourdomain.com/phpmyadmin.

Security: Restrict phpMyAdmin access by IP address or protect it with HTTP authentication. Publicly exposed phpMyAdmin installations are heavily targeted by bots.

Install WordPress

pi@raspberrypi:~$ cd /tmp
pi@raspberrypi:/tmp$ wget https://wordpress.org/latest.tar.gz
pi@raspberrypi:/tmp$ tar -xzf latest.tar.gz
pi@raspberrypi:/tmp$ sudo cp -r wordpress/* /var/www/mywebsite/
pi@raspberrypi:/tmp$ sudo chown -R www-data:www-data /var/www/mywebsite/

Then visit your domain in a browser and follow the WordPress setup wizard. Use the database name, username, and password you created in Step 4.

Quick Reference

TaskCommand
Start Apachesudo systemctl start apache2
Stop Apachesudo systemctl stop apache2
Reload Apache configsudo systemctl reload apache2
Test Apache configsudo apache2ctl configtest
View Apache error logsudo tail -f /var/log/apache2/error.log
View Apache access logsudo tail -f /var/log/apache2/access.log
Enable a sitesudo a2ensite sitename.conf
Disable a sitesudo a2dissite sitename.conf
Enable a modulesudo a2enmod modulename
Renew SSL certificatesudo certbot renew
Check cert expirysudo certbot certificates
Log in to MariaDBsudo mysql -u root -p
Web root directory/var/www/mywebsite/
Apache config directory/etc/apache2/sites-available/