Raspberry Pi Web Server Setup

Did you know that a Raspberry Pi can handle thousands of daily visitors and serve as a production-ready web server? With the right setup, this tiny computer can power websites, web applications, and even serve as your personal development environment.

In this comprehensive guide, I'll walk you through setting up a robust Nginx server with PHP on your Raspberry Pi.

Why Choose Raspberry Pi for Web Hosting?

Before we dive into the technical setup, let's understand why Raspberry Pi makes an excellent web server:

  • Cost-effective: A complete setup costs under €100, compared to hundreds for traditional hosting
  • Energy efficient: Uses only 2-5 watts of power, perfect for 24/7 operation
  • Full control: Complete ownership of your server and data
  • Learning opportunity: Great way to understand server administration
  • Scalable: Can handle multiple websites and applications

Prerequisites

Before starting, ensure you have:

  • Raspberry Pi (3B+, 4B, or newer recommended)
  • MicroSD card (32GB or larger, Class 10)
  • Power supply for your Pi
  • Network connection (Ethernet or WiFi)
  • Raspberry Pi OS (formerly Raspbian) installed

Step 1: Install Raspberry Pi OS

First, you need to install Raspberry Pi OS on your MicroSD card. You can download the latest version from the Raspberry Pi Imager at https://www.raspberrypi.com/software/.

Install Raspberry Pi Imager and follow the instructions to install Raspberry Pi OS Lite 32bit on your MicroSD card. Enable SSH and set up wifi connection if needed.

Insert the MicroSD card into your Raspberry Pi and power it on.

Step 2: Connect to your Raspberry Pi

Connect to your Raspberry Pi using SSH or a serial console. You can use the following command to connect to your Raspberry Pi:

                    pi@raspberrypi:~$ssh [your_username]@[your_pi_ip]

If using Windows, you can use PuTTY to connect to your Raspberry Pi.

Step 3: Update Your System

First, let's ensure your Raspberry Pi is up to date:

                    pi@raspberrypi:~$sudo apt update && sudo apt full-upgrade
sudo apt install unattended-upgrades
sudo dpkg-reconfigure --priority=low unattended-upgrades

This ensures you have the latest security patches and software versions and enables automatic security updates.

Step 4: Install Firewall

Install and configure UFW (Uncomplicated Firewall) to secure your Raspberry Pi:

                    pi@raspberrypi:~$sudo apt install ufw
sudo ufw allow ssh
sudo ufw allow http
sudo ufw allow https
sudo ufw enable

This will allow SSH, HTTP, and HTTPS traffic to your Raspberry Pi.

Step 5: Install Nginx

Nginx is a high-performance web server that's perfect for Raspberry Pi:

                    pi@raspberrypi:~$sudo apt install nginx

After installation, start and enable Nginx:

                    pi@raspberrypi:~$sudo systemctl start nginx
sudo systemctl enable nginx

Test if Nginx is running by visiting your Pi's IP address in a browser. You should see the default Nginx welcome page.

Step 6: Install PHP and PHP-FPM

PHP-FPM (FastCGI Process Manager) is the recommended way to run PHP with Nginx:

                    pi@raspberrypi:~$sudo apt install php php-fpm

This installs PHP 8.x (latest stable version) along with common extensions.

View the installed PHP version:

                    pi@raspberrypi:~$ls /var/run/php/

Start and enable PHP-FPM:

                    pi@raspberrypi:~$sudo systemctl start php8.2-fpm
sudo systemctl enable php8.2-fpm

Note: Replace "8.2" with your installed PHP version if different.

Step 7: Install Certbot

Install Certbot to manage SSL certificates:

                    pi@raspberrypi:~$sudo apt install certbot python3-certbot-nginx

This will install Certbot and the Nginx plugin.

Step 8: Port Forwarding

If you want to access your Raspberry Pi from outside your local network, you need to forward ports. For example, if you want to access your Raspberry Pi from your phone, you need to forward port 80 to your Raspberry Pi.

Forward ports 22 (SSH), 80 (HTTP) and 443 (HTTPS) on your router to your Raspberry Pi’s local IP.

Step 9: Domain Name

You need a domain name (e.g., mysite.com) that points to your Pi’s public IP.

Use a Dynamic DNS provider if your IP changes (e.g., DuckDNS, No-IP).

Update DNS A-record to point to your public IP.

Step 10: Make a directory for your website(s)

Create a directory for your website(s):

                    pi@raspberrypi:~$sudo mkdir -p /var/www/mysite.com/html

If creating multiple websites, you can create a directory for each website:

                    pi@raspberrypi:~$sudo mkdir -p /var/www/mysite1.com/html
sudo mkdir -p /var/www/mysite2.com/html

Set permissions:

                    pi@raspberrypi:~$sudo chmod -R 777 /var/www/mysite.com/html
sudo chown -R www-data:www-data /var/www/mysite.com/html

If creating multiple websites, you can set permissions for each website:

                    pi@raspberrypi:~$sudo chmod -R 777 /var/www/mysite1.com/html
sudo chown -R www-data:www-data /var/www/mysite1.com/html
sudo chmod -R 777 /var/www/mysite2.com/html
sudo chown -R www-data:www-data /var/www/mysite2.com/html

Step 11: Configure Nginx website(s)

Create a new server block configuration:

                    pi@raspberrypi:~$sudo nano /etc/nginx/sites-available/mysite.com

Add your site configuration:

                    pi@raspberrypi:~$server {
    listen 80;
    server_name mysite.com www.mysite.com;
                
    root /var/www/mysite.com/html;
    index index.php index.html index.htm;
                
    location / {
        try_files $uri $uri/ =404;
    }

    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/run/php/php8.2-fpm.sock;
    }
}

If creating multiple websites, you can create a new server block configuration for each website:

                    pi@raspberrypi:~$sudo nano /etc/nginx/sites-available/mysite1.com

Add your site configuration:

                    pi@raspberrypi:~$server {
    listen 80;
    server_name mysite1.com www.mysite1.com;
                
    root /var/www/mysite1.com/html;
    index index.php index.html index.htm;
                
    location / {
        try_files $uri $uri/ =404;
    }

    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/run/php/php8.2-fpm.sock;
    }
}

Second website:

                    pi@raspberrypi:~$sudo nano /etc/nginx/sites-available/mysite2.com

Add your site configuration:

                    pi@raspberrypi:~$server {
    listen 80;
    server_name mysite2.com www.mysite2.com;
                
    root /var/www/mysite2.com/html;
    index index.php index.html index.htm;
                
    location / {
        try_files $uri $uri/ =404;
    }

    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/run/php/php8.2-fpm.sock;
    }
}

Step 12: Enable the website(s)

Enable the website(s):

                    pi@raspberrypi:~$sudo ln -s /etc/nginx/sites-available/mysite.com /etc/nginx/sites-enabled/
sudo ln -s /etc/nginx/sites-available/mysite1.com /etc/nginx/sites-enabled/
sudo ln -s /etc/nginx/sites-available/mysite2.com /etc/nginx/sites-enabled/

Test the configuration:

                    pi@raspberrypi:~$sudo nginx -t

If the test passes, reload Nginx:

                    pi@raspberrypi:~$sudo systemctl reload nginx

Visit your website(s) in your browser. You should see the website(s) running.

Step 13: SSL Certificate with Let's Encrypt

Issue an SSL certificate for your website(s):

                    pi@raspberrypi:~$sudo certbot --nginx -d mysite.com -d www.mysite.com
sudo certbot --nginx -d mysite1.com -d www.mysite1.com
sudo certbot --nginx -d mysite2.com -d www.mysite2.com

This will issue a certificate for your website(s) and configure Nginx to use it.

Step 14: Upload your website(s)

Upload your website(s) to the /var/www/mysite.com/html directory.

If creating multiple websites, you can upload your website(s) to the /var/www/mysite1.com/html and /var/www/mysite2.com/html directories.

You can use FTP, SFTP, or SCP to upload your website(s).

You schould now be possible to access your website(s) at https://mysite.com, https://mysite1.com and https://mysite2.com.

Step 15: Install and configure php mail

Install Mail Transfer Agent (MTA):

                    pi@raspberrypi:~$sudo apt install msmtp msmtp-mta bsd-mailx

Configure msmtp:

                    pi@raspberrypi:~$nano /etc/msmtprc

Add the following to the file:

                    pi@raspberrypi:~$defaults
auth on
tls on
tls_trust_file /etc/ssl/certs/ca-certificates.crt
logfile /var/log/msmtp.log
                        
account mail_one
host smtp.gmail.com
port 587
from [your_email]
user [your_email]
password "[your_password]"
                        
account default : mail_one

Edit the file to your needs.

Set permissions:

                    pi@raspberrypi:~$sudo chown root:root /etc/msmtprc
chmod 777 /etc/msmtprc

Link msmtp to sendmail:

                    pi@raspberrypi:~$sudo ln -s /usr/bin/msmtp /usr/sbin/sendmail

Add path to sendmail to php.ini:

                    pi@raspberrypi:~$sudo nano /etc/php/8.2/fpm/php.ini

Add the following to the file:

                    pi@raspberrypi:~$sendmail_path = /usr/bin/msmtp -t

Restart PHP-FPM:

                    pi@raspberrypi:~$sudo systemctl restart php8.2-fpm

Restart Nginx:

                    pi@raspberrypi:~$sudo systemctl restart nginx

You should now be able to send emails from your website.

Step 16: Fail2ban

Fail2ban is a tool that can help yu protect your server from brute force attacks.

Install Fail2ban:

                    pi@raspberrypi:~$sudo apt install fail2ban
sudo systemctl start fail2ban
sudo systemctl enable fail2ban

Configure Fail2ban:

                    pi@raspberrypi:~$sudo nano /etc/fail2ban/jail.local

Add the following to the file:

                    pi@raspberrypi:~$[DEFAULT]
# who gets the alerts (comma-separated works)
destemail  = [your_email]
# who it's from
sender     = fail2ban@[your_domain]
sendername = Fail2Ban
# If you installed postfix, leave mta=sendmail (default).
# If you installed msmtp + msmtp-mta, it also provides /usr/sbin/sendmail, so keep sendmail here too.
mta        = sendmail
# Choose the email action:
#  - %(action_mw)s  : ban + whois report + email
#  - %(action_mwl)s : ban + whois + log lines + email (more verbose)
#  - %(action)s     : ban only, no email
action     = %(action_mwl)s
                        
banaction           = ufw
banaction_allports  = ufw
findtime = 10m
# 36d = 36 days / -1 = permanent ban
bantime  = 30m
maxretry = 5
                        
# Ignore localhost, IPv6 loopback, and all 192.168.* addresses
ignoreip = 127.0.0.1/8 ::1 192.168.0.0/16
                        
# Use journald globally, then override per-jail when needed
backend = systemd
                        
# ---- SSH over journald ----
[sshd]
enabled       = true
port          = ssh
backend       = systemd
journalmatch  = _SYSTEMD_UNIT=ssh.service
maxretry      = 3
bantime       = -1
                        
# ---- Enable built-in nginx filters (recommended) ----
[nginx-http-auth]
enabled = true
port    = http,https
backend = auto
logpath = /var/log/nginx/access.log
                        
[nginx-botsearch]
enabled = true
port    = http,https
backend = auto
logpath = /var/log/nginx/access.log
                        
[recidive]
enabled = true
logpath = /var/log/fail2ban.log
backend = auto
bantime = -1        ; permanent
findtime = 86400    ; 1 day

Edit destemail, sender and ignoreip to your needs.

Create filters for nginx-http-auth:

                    pi@raspberrypi:~$sudo nano /etc/fail2ban/filter.d/nginx-http-auth.conf

Add the following to the file:

                    pi@raspberrypi:~$# fail2ban filter configuration for nginx

[INCLUDES]
                        
before = nginx-error-common.conf
                        
[Definition]
                        
mode = normal
                        
__err_type = <_ertp-<mode>>
                        
_ertp-auth = error
mdre-auth = ^%(__prefix_line)suser "(?:[^"]+|.*?)":? (?:password mismatch|was not found in "[^\"]*"), client: <HOST>, server: \S*, request: "\S+ \S+ HTTP/\d+\.\d+", host: "\S+"(?:, referrer: "\S+")?\s*$
_ertp-fallback = crit
mdre-fallback = ^%(__prefix_line)sSSL_do_handshake\(\) failed \(SSL: error:\S+(?: \S+){1,3} too (?:long|short)\)[^,]*, client: <HOST>
                        
_ertp-normal = %(_ertp-auth)s
mdre-normal = %(mdre-auth)s
_ertp-aggressive = (?:%(_ertp-auth)s|%(_ertp-fallback)s)
mdre-aggressive = %(mdre-auth)s
                    %(mdre-fallback)s
                        
failregex = <mdre-<mode>>
                        
ignoreregex =
                        
datepattern = {^LN-BEG}
                        
journalmatch = _SYSTEMD_UNIT=nginx.service + _COMM=nginx
                        
# DEV NOTES:
# mdre-auth:
# Based on samples in https://github.com/fail2ban/fail2ban/pull/43/files
# Extensive search of all nginx auth failures not done yet.
#
# Author: Daniel Black
                        
# mdre-fallback:
# Ban people checking for TLS_FALLBACK_SCSV repeatedly
# https://stackoverflow.com/questions/28010492/nginx-critical-error-with-ssl-handshaking/28010608#28010608
# Author: Stephan Orlowsky

Create filters for nginx-botsearch:

                    pi@raspberrypi:~$sudo nano /etc/fail2ban/filter.d/nginx-botsearch.conf

Add the following to the file:

                    pi@raspberrypi:~$# Fail2Ban filter to match web requests for selected URLs that don't exist
#
                        
[INCLUDES]
                        
# Load regexes for filtering
before = botsearch-common.conf
                        
[Definition]
                        
failregex = ^<HOST> \- \S+ \[\] \"(GET|POST|HEAD) \/<block> \S+\" 404 .+$
            ^ \[error\] \d+#\d+: \*\d+ (\S+ )?\"\S+\" (failed|is not found) \(2\: No such file or directory\), client\: <HOST>\, server\: \S*\, request: \"(GET|POST|HEAD) \/<block> \S+\"\, .*?$
                        
ignoreregex =
                        
datepattern = {^LN-BEG}%%ExY(?P<_sep>[-/.])%%m(?P=_sep)%%d[T ]%%H:%%M:%%S(?:[.,]%%f)?(?:\s*%%z)?
                ^[^\[]*\[({DATE})
                {^LN-BEG}
                        
journalmatch = _SYSTEMD_UNIT=nginx.service + _COMM=nginx
                        
# DEV Notes:
# Based on apache-botsearch filter
#
# Author: Frantisek Sumsal

Test the configuration:

                    pi@raspberrypi:~$sudo fail2ban-client -t

Schould return: OK: configuration test is successful

You can now start Fail2ban:

                    pi@raspberrypi:~$sudo fail2ban-client reload

You should now receive emails if someone tries to brute force your server.

You can also check the status of jails from Fail2ban:

                    pi@raspberrypi:~$sudo fail2ban-client status

View banned ip's in the jail:

                    pi@raspberrypi:~$sudo fail2ban-client status nginx-http-auth

Unban an ip:

                    pi@raspberrypi:~$sudo fail2ban-client unban ip_address

You can also check the logs of Fail2ban:

                    pi@raspberrypi:~$sudo cat /var/log/fail2ban.log

Step 17: Monitoring and Maintenance

Keep your server healthy with these commands:

                    pi@raspberrypi:~$sudo systemctl status nginx
sudo systemctl status php8.2-fpm
sudo fail2ban-client status
sudo fail2ban-client status sshd

View logs:

                    pi@raspberrypi:~$sudo tail -f /var/log/nginx/access.log
sudo tail -f /var/log/nginx/error.log
journalctl -u ssh
sudo tail -f /var/log/fail2ban.log

Common Issues and Solutions

Issue 1: PHP Files Not Processing

Symptoms: PHP code shows as plain text

Solution: Check PHP-FPM socket path and permissions

                    pi@raspberrypi:~$sudo ls -la /var/run/php/
sudo chown www-data:www-data /var/run/php/php8.2-fpm.sock

Issue 2: Permission Denied Errors

Symptoms: 403 Forbidden errors

Solution: Fix file permissions

                    pi@raspberrypi:~$sudo chown -R www-data:www-data /var/www/html
sudo chmod -R 755 /var/www/html

Issue 3: High Memory Usage

Symptoms: Server becomes slow or unresponsive

Solution: Optimize PHP-FPM settings and add swap space

                    pi@raspberrypi:~$sudo fallocate -l 1G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile

Performance Tips

  • Use SSD storage: If possible, use an external SSD for better I/O performance
  • Enable caching: Implement Redis or Memcached for dynamic content
  • Optimize images: Use WebP format and implement lazy loading
  • Minimize plugins: Only install necessary PHP extensions
  • Regular updates: Keep your system and software updated

Conclusion

Congratulations! You've successfully set up a production-ready Nginx server with PHP on your Raspberry Pi. This setup can handle:

  • Personal blogs and portfolios
  • Small business websites
  • Development and testing environments
  • Web applications and APIs
  • Multiple websites with virtual hosting

Your Raspberry Pi web server is now ready to serve websites to the world. Remember to:

  • Regularly backup your data
  • Monitor system resources
  • Keep software updated
  • Monitor logs for issues
  • Consider setting up automated backups

Ready to take your web development skills to the next level? This Raspberry Pi setup is perfect for learning server administration, testing web applications, and even hosting small production websites.

Need Help with Your Web Server Setup?

If you need assistance with server configuration, optimization, or have questions about hosting your website, I'm here to help. Contact me for professional web development and server administration services.

Get Professional Help