New to Rust? Grab our free Rust for Beginners eBook Get it free →
Raspberry Pi Web Server Setup

On a home network, you can open a small website from another device before deciding whether to make it public. Apache is web server software that lets a Raspberry Pi return website files to a browser.
I’ll show you how to install Apache on Raspberry Pi OS and serve a static page on your home network. I’ll also explain why making it public takes separate network and security work.
TL;DR: Set Up a Raspberry Pi Web Server
A Raspberry Pi running Raspberry Pi OS can serve a static website on your local network with Apache, a web server package available for Debian-based systems.
- Install Raspberry Pi OS with Raspberry Pi Imager and set a username, password, hostname, and network before the first boot.
- Update packages, install Apache, and place an index.html file under Apache’s document root.
- Open the Pi’s local address from another device on the same network to verify the page.
- Treat public internet access as a separate networking and security project, not as a side effect of running Apache.
What Is a Raspberry Pi Web Server?
A web server is software that receives network requests and returns a response to a client. In this example, a Raspberry Pi runs Apache to serve a small website.
Static pages suit a portfolio, project page, or read-only dashboard because Apache can return stored files without running application logic for every request.
A browser resolves the hostname, connects to the web service, and sends a path such as /about.html. Apache selects a site configuration and maps that path under its document root, the base directory for website files. With /var/www/html as the root, /about.html maps to /var/www/html/about.html, while a directory URL can select a configured index.html file.
A local-area network, or LAN, connects devices within a home or office.
| Where the browser is | Address it uses | What must be configured |
|---|---|---|
| On the Pi itself | http://localhost/ | Apache is installed and running |
| Another device on the same LAN | Pi’s private IP or local hostname | Both devices are on the LAN and the web port is reachable |
| Outside the LAN | A public hostname or address | Internet routing, name resolution, encryption, and access controls |
The address localhost always refers to the computer running the browser or command that uses it. If you type it on a laptop, it tests the laptop, not the Pi. Use it on the Pi itself for a local service check, then use the Pi’s private address when the client is another device.
A private address is for devices on your local network, whereas a public address identifies the connection other networks can reach. DNS translates a hostname to an address, but it does not create a router or provider route to Apache.
A dynamic application runs program logic for each request and typically relies on a runtime or database. Apache can route selected requests to that service while serving static assets separately.
- Use the Pi’s local IP when testing from another device on the same home network.
- Use a public hostname only after arranging an internet route and the security controls for it.
How to Set Up Apache on Raspberry Pi OS Step by Step
Prepare Raspberry Pi OS, install Apache, put a small HTML page in the configured document root, then test it from another device on the same network.
- A Raspberry Pi computer, a compatible power supply, and a microSD card or other supported boot media.
- A second computer for Raspberry Pi Imager and network testing.
- Wi-Fi credentials or an Ethernet connection for the Pi.
- A network where the client device can reach the Pi. A monitor, keyboard, and mouse are optional when remote shell access is configured during imaging.
Step 1: Prepare Raspberry Pi OS and SSH
Use Raspberry Pi Imager to write Raspberry Pi OS to the boot media. Set a hostname and create your own username and password in its configuration options. If the server will run without a screen, also configure its network and enable SSH there.
Before writing the boot card, review Imager’s summary for the OS release, hostname, wireless network, and account. Choose a new password in its local setup fields rather than relying on historical defaults or placing credentials in commands.
Boot the Pi and allow it to join the configured network. If you enabled SSH, connect from a computer on that network using the account and hostname you set. Replace the sample hostname in the command if you chose another.
A headless installation still needs a working network connection before a remote shell can reach it. If the Pi is not listed by the router after boot, check Ethernet or Wi-Fi settings, the saved network name, and power before changing Apache configuration.
If wireless details change after the boot card is prepared, the Pi may start normally but fail to join the expected network. Recheck the saved network settings or attach a display and keyboard to correct them before troubleshooting SSH or Apache.
The .local name relies on multicast DNS and may not resolve on every client or network. Check the router’s connected-device list or the Pi’s network menu for its address, and consult Raspberry Pi’s remote-access documentation if SSH discovery still fails. A name lookup failure alone does not show that Apache is unavailable.
The first SSH connection may ask you to accept a host key. That key helps the SSH client recognize the device on later connections. If the key changes unexpectedly, pause and verify that the address still belongs to your Pi before entering credentials.
If you use a display instead of SSH, open the terminal application on Raspberry Pi OS and run the later commands there. The package and service steps are the same. The difference is only how you reach the Pi’s shell.
Step 2: Update Raspberry Pi OS
Update the package index and installed packages before adding the web server. Run this on the Pi’s shell, not on the computer used to prepare its SD card.
sudo apt update
sudo apt full-upgrade
The first command refreshes repository metadata without installing software. The full-upgrade command changes installed packages, so review its proposed transaction before accepting it. For an established Pi, make a backup and choose a time when a restart is acceptable.
Keep the terminal attached until package installation finishes and read any prompt before confirming. If the package manager reports an error, record the exact package name and message rather than repeating the command blindly. A partial upgrade can leave other work unfinished.
APT uses the repositories configured for the installed Raspberry Pi OS release. Avoid adding an unrelated repository just to obtain a newer-looking version, since packages built for another release can change dependencies or configuration paths.
Step 3: Install and check Apache
Install apache2, the Debian package for Apache, from the configured Raspberry Pi OS repositories.
sudo apt install apache2
The package manager may start Apache during installation. If it does not, start and enable the service so it begins on future boots. These commands change service state and must be run on the Pi by an administrator.
sudo systemctl enable --now apache2
Run this read-only query on the Pi before moving on to the browser request.
The standard Apache site serves a default page until you replace or extend its site configuration. Installing the package does not copy your own project into the web directory, create application accounts, or make the Pi publicly reachable.
A service can be active now without being enabled to start at every boot. Those are separate service-manager states, so check the first to diagnose a current failure and the second only when you need the site to return after a restart.
systemctl is-active apache2
Step 4: Publish a static page
Before replacing the sample, inspect the enabled site configuration if you changed its default paths. Keep a copy of the stock page so you can restore it if needed.
Create an index.html page with a text editor on the Pi, or transfer a prepared file into the document root. This small page gives the browser something distinct to request:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Raspberry Pi Web Server</title>
</head>
<body>
<h1>Hello from my Raspberry Pi</h1>
<p>Apache is serving this page on my local network.</p>
</body>
</html>
Before copying the page to the Pi, I ran a small Python check on the saved HTML sample. It confirmed the title and heading shown below, but it did not test Apache or the network. Save the listing as verify_page.py beside index.html and run it with Python 3.
from html.parser import HTMLParser
from pathlib import Path
class PageSummary(HTMLParser):
def __init__(self):
super().__init__()
self.in_title = False
self.in_h1 = False
self.title = []
self.heading = []
def handle_starttag(self, tag, attrs):
self.in_title = tag == "title"
self.in_h1 = tag == "h1"
def handle_endtag(self, tag):
if tag == "title":
self.in_title = False
if tag == "h1":
self.in_h1 = False
def handle_data(self, data):
if self.in_title:
self.title.append(data.strip())
if self.in_h1:
self.heading.append(data.strip())
page = PageSummary()
page.feed(Path("index.html").read_text(encoding="utf-8"))
print("Page title:", " ".join(page.title))
print("Page heading:", " ".join(page.heading))
Run the exact command shown below after saving both files in the same directory.
python3 verify_page.py
I ran the parser against the saved HTML sample. It printed:
Page title: Raspberry Pi Web Server
Page heading: Hello from my Raspberry Pi

Copy the prepared file into Apache’s default document root. This command changes a protected system directory, so run it on the Pi with administrator access, not on the computer used to prepare the sample.
sudo install -m 0644 ./index.html /var/www/html/index.html
The command copies only this file and grants read access for serving it. Keep private keys, backups, configuration files, and credentials outside any directory Apache serves.
Keep linked stylesheets, images, and scripts in the same published tree, using paths that match their directory names. The browser requests each asset separately, so a page can load while a missing stylesheet or image produces a partial result. When you replace only a static file, Apache normally reads the new file on the next request without a service restart.
Use lower-case, predictable names for files and links, and test one image and stylesheet alongside the HTML before copying a larger project. If the page looks unstyled, open the browser’s developer tools or request the missing asset directly to see whether its path returns a file or an error.
After Apache is running, find the Pi’s current private address in Raspberry Pi OS network settings or the router’s connected-device list. Open http://PI_PRIVATE_IP/ from a phone or computer on the same home network, replacing the placeholder with the address shown for your Pi. No Raspberry Pi was attached during this draft, so this device check has no captured result.
If you already have a website, serve its completed output directory rather than exposing a development server or build process. Confirm it includes the CSS, images, and other assets that the page references.
For a second site, add an Apache virtual-host entry only after confirming the default page works.
The Pi’s private address can change when its DHCP lease changes. For a stable bookmark, reserve an address in the router or use a local hostname supported by your network. Router menus vary, so follow the router’s own instructions.
Ping and HTTP test different protocols. Some devices block ICMP echo while still allowing web traffic, so use the browser request to check the page itself.
Raspberry Pi Web Server Problems and Public Access
Check the layer that failed before changing settings: Apache service, file path and permissions, LAN reachability, then optional internet routing. Use the table to choose the next check.
| Symptom | Likely area | Next check |
|---|---|---|
| Connection refused at localhost | Apache is stopped or listening on a different port | Check service state and Apache listener configuration |
| Default page appears instead of your file | Wrong document root, filename, or virtual host | Check active DocumentRoot and index filename |
| HTTP 403 Forbidden | Apache cannot read a file or traverse a parent directory | Inspect permissions on the file and each parent path |
| Pi loads its own page but another LAN device times out | Wrong private IP, Wi-Fi guest isolation, or firewall rule | Confirm both clients share a network and retry the Pi’s current address |
| LAN works but outside connection times out | Router, ISP, public IP, or CGNAT path | Do not assume Apache is broken. Test from a separate network and review exposure options |
| One site works but a second hostname shows the first site | DNS or Apache virtual-host selection | Check the requested hostname, DNS record, and virtual-host order |
Read the status code and Apache logs
If Apache fails after a configuration edit, run the administrator check below on the Pi before changing network settings.
sudo apachectl configtest
A syntax error identifies a file and line to inspect. Correct that specific directive rather than replacing the full configuration with a file copied from a different OS release.
For a service-start failure, inspect recent logs on the Pi for a configuration, port, or file-access error.
Apache’s access log records requests and response codes, while its error log records server-side diagnostics. If the request never appears in the access log, first check whether it reached the Pi. Keep raw logs private because they can contain client addresses and requested paths.
sudo journalctl -u apache2 --no-pager -n 50
Identify the service that owns a busy port before changing it. Disabling an unknown service can interrupt another application on the Pi.
Separate LAN problems from public routing
Work outward from the Pi before blaming the internet connection. A local browser response, a second device on the home network, and a phone on mobile data test different parts of the route.
| Observation | Likely layer | Next check |
|---|---|---|
| Pi page opens only on the Pi | Client address or local network | Use the Pi’s current private address from another device |
| Other home devices cannot connect | Guest Wi-Fi, client isolation, or firewall | Confirm the network permits peer traffic and review existing firewall rules |
| Connection is refused | No service accepts that address and port | Check Apache’s service state and the configured listener |
| HTTP 404 | URL path or site mapping | Check the requested path, filename capitalization, and active document root |
| HTTP 403 | File or directory access | Confirm Apache can read the file and traverse parent directories |
| HTTP 5xx | Server or application processing | Read the corresponding server-side error log |
| Outside connection times out | Router, address translation, or internet provider | Review the inbound route and ask whether the provider permits it |
Do not respond to a 403 error by making the entire website writable by every account. Apache needs permission to read the requested file and traverse its parent directories, not permission to edit the site content.
Guest Wi-Fi or client-isolation settings can block local requests. Check the router’s instructions and the firewall configuration already in use. Add only the narrow web rule the network needs.
For a local check, start with the Pi’s numeric address before testing a hostname. A timeout often points to a route or filter, while an immediate refusal means the address answered without accepting that web connection. Confirm that the address belongs to the Pi’s active network interface and that the browser is using HTTP for this initial test.
Plan before exposing the page publicly
A public request must pass through name resolution, the provider’s network, the home router, and Apache. Decide which route you intend to use before changing the server or router.
| Goal | Typical approach | Review before using it |
|---|---|---|
| Open the page at home | Private address or local hostname | Address stability and device isolation |
| Reach a private dashboard remotely | VPN or authenticated tunnel | Who can join and which services become reachable |
| Publish a public website | Managed hosting or a secured public endpoint | TLS, updates, authentication, backups, and monitoring |
A home router uses network address translation so several devices can share an internet connection. A port-forwarding rule matches inbound traffic on a chosen external port and sends it to a device and port on the home network. Keep the Pi’s local address stable, and remember that a rule on only the inner of two routers may not complete the path.
The router’s forwarding rule and the Pi’s own firewall are separate controls. A correct router entry does not guarantee the host accepts the connection, and a host rule does not create an inbound route through the provider. Avoid placing the whole Pi in a router’s DMZ as a shortcut.
Use that rule only for a service you intend to expose. Anyone who can reach the address can attempt a connection, so check its content, authentication, updates, and recovery plan first.
- Record the router rule, destination device, and port so you can reverse the change later.
- Confirm that your internet provider supplies a reachable public address before adjusting Apache.
- Test the hostname from a connection outside your home network.
Compare the router’s WAN address with the public address reported by your provider or an external address checker. A mismatch can mean carrier-grade NAT or another upstream router, where a rule on your own router cannot control the outer route. Ask the provider whether inbound connections are supported before changing Apache settings.
Dynamic DNS updates a hostname when a reachable public address changes, and an internet-facing name should resolve to the address visitors can reach. An A record names an IPv4 address and an AAAA record names an IPv6 address, but the selected route and firewall still need to permit the web connection. DNS changes can take time to appear in client caches, so test from mobile data rather than assuming an in-home loopback failure means the public route is broken.
A reverse proxy can accept HTTPS, select an application by hostname or path, and forward requests to an origin on the private side. It helps organize multiple services, but it does not replace application authentication or remove the need to patch each service behind it.
Keep each origin service bound to the interface it needs and expose only the proxy when the architecture supports that arrangement. The proxy becomes another configuration layer, so verify the requested hostname, destination service, and certificate together when a routed site fails.
A small static page or household dashboard can be a good learning project on a Pi. For a public site that needs dependable uptime, handles many visitors, or stores account data, compare managed hosting with the power, internet connection, storage, patching, and recovery work of operating the service at home.
The Pi gives you control over the device and a useful way to learn Linux administration. Managed hosting shifts hardware replacement and much of the network operation to a provider, but still leaves you responsible for application updates, access policy, and backups.
A VPN gives approved devices a route into a private network, so members may reach more than the website unless rules limit them. A service-specific outbound tunnel usually publishes one endpoint instead. Review how the provider handles identity, encryption, logs, and revocation, and consult Dataplicity’s Wormhole documentation for an example of one tunnel option.
A website request and an SSH session have different reach: keep shell access on a trusted LAN or behind a VPN unless you separately maintain public administration.
HTTPS protects browser-to-server traffic and lets a browser verify a hostname, but it does not authenticate users or repair vulnerable code. A certificate must match the name in the address bar, so a raw-IP test can show a name warning. Plan renewal before expiry and protect the connection between a reverse proxy and the Pi if that hop crosses a shared network.
Home hosting also depends on power, router uptime, and the internet plan’s upload capacity. A brief outage or scheduled reboot can make the page unavailable to every visitor, so decide whether that downtime is acceptable. Keep an off-device backup and test a restore, including changing site data, before relying on the Pi for anything important.
Apache uses the hostname in a request to choose a named virtual host. If one name shows another site’s page, confirm that DNS or the client’s local resolver points to the expected address and that the matching Apache site entry is enabled. A hosts-file override affects only that client and does not publish the name publicly.
Conclusion: Keep the First Server on Your LAN
Start with one static page and one local network path. Add a second site, an application runtime, or public access only when the project needs that extra layer.
Once the static page works, the next step depends on the project. See the Raspberry Pi and Node.js installation guide to explore a server-side application, or read the Raspberry Pi networking article for a separate network-routing example. The latter covers older redirection techniques, so do not copy its system-level commands into this basic web-server setup.
- Raspberry Pi getting started documentation
- Apache URL mapping and DocumentRoot documentation
- Raspberry Pi remote access documentation
FAQ
These answers clarify the setup choices and the difference between local testing and internet access.
Can a Raspberry Pi be used as a web server?
Yes. A Raspberry Pi running Raspberry Pi OS can run Apache and serve static files to devices on its local network. Public access needs separate routing and security work.
How do I find my Raspberry Pi’s local IP address?
Check the network menu in Raspberry Pi OS or the router’s connected-device list. Use the private address from a device on the same LAN.
Can I access my Raspberry Pi web server from outside my home?
You can, but local Apache setup alone is not enough. You need an internet route such as carefully configured port forwarding or a tunnel, plus a stable name and appropriate security. Do not expose SSH as a shortcut.
Where should I put my website files for Apache?
On a common Debian-based Apache setup, the document root is /var/www/html. Check the active Apache configuration because custom installations can use another path.




