Install FastAPI and Uvicorn Create systemd service * Configure Apache proxy
First, make sure the system’s Python environment is clean and isolated. Ubuntu ships with Python 3, so you can create a virtual environment in the project directory and keep the dependencies separate from the OS packages.
# Create a project folder
mkdir -p /opt/fastapi-demo && cd /opt/fastapi-demo
# Set up an isolated venv
python3 -m venv venv
source venv/bin/activate
# Install FastAPI and Uvicorn
pip install --upgrade pip
pip install fastapi uvicorn
After the installation finishes, verify the import works:
>>> from fastapi import FastAPI
>>> import uvicorn
>>> print(FastAPI, uvicorn.__version__)
<class 'fastapi.applications.FastAPI'> 0.24.0
Now add a minimal “hello world” endpoint so the service has something to run:
# app/main.py
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def read_root():
return {"message": "FastAPI is up"}
Leave the virtual environment active while you test the server locally:
uvicorn app.main:app --host 127.0.0.1 --port 8000
If you see {"message":"FastAPI is up"} at http://127.0.0.1:8000/, the core stack is ready. The next steps will wrap this process in a systemd unit and expose it through Apache’s reverse proxy.
Place the service file in /etc/systemd/system. Using a dedicated user isolates the app from the rest of the system and avoids accidental privilege escalation.
# Create a low‑privilege user for the app
sudo useradd -r -s /usr/sbin/nologin fastapi
# Copy the virtual environment into the user’s home (optional)
sudo mkdir -p /var/www/fastapi-demo
sudo cp -r /opt/fastapi-demo/* /var/www/fastapi-demo/
sudo chown -R fastapi:fastapi /var/www/fastapi-demo
Now write the unit file. The ExecStart line launches Uvicorn from the virtual environment, binding to a Unix socket that Apache will forward to.
sudo tee /etc/systemd/system/fastapi.service > /dev/null <<'EOF'
[Unit]
Description=FastAPI application
After=network.target
[Service]
User=fastapi
Group=fastapi
WorkingDirectory=/var/www/fastapi-demo
Environment="PATH=/var/www/fastapi-demo/venv/bin"
ExecStart=/var/www/fastapi-demo/venv/bin/uvicorn main:app \
--uds /run/fastapi.sock \
--workers 4 \
--log-level info
Restart=on-failure
LimitNOFILE=65535
[Install]
WantedBy=multi-user.target
EOF
Reload systemd, enable the service, and start it:
sudo systemctl daemon-reload
sudo systemctl enable --now fastapi.service
Verify that the socket appears and the process is running:
sudo ss -xl | grep fastapi.sock
sudo systemctl status fastapi.service
If the service fails, inspect the logs with journalctl -u fastapi.service. Once the socket is live, Apache can proxy to /run/fastapi.sock without exposing a raw port.
Enable the required Apache modules and create a dedicated virtual host that forwards traffic to the Uvicorn socket. Start by loading the proxy stack:
sudo a2enmod proxy proxy_http proxy_balancer lbmethod_byrequests
sudo systemctl restart apache2
Next, add a site definition under /etc/apache2/sites-available/fastapi.conf. The file points Apache at the Unix socket created by the systemd service (see the previous section). Using a socket avoids an extra TCP hop and keeps the connection local.
<VirtualHost *:80>
ServerName api.example.com
# Forward all requests to the FastAPI socket
ProxyPreserveHost On
ProxyPass / unix:/run/fastapi.sock|http://localhost/
ProxyPassReverse / unix:/run/fastapi.sock|http://localhost/
# Optional: serve static files directly
Alias /static /var/www/fastapi-demo/static
<Directory /var/www/fastapi-demo/static>
Require all granted
</Directory>
ErrorLog ${APACHE_LOG_DIR}/fastapi_error.log
CustomLog ${APACHE_LOG_DIR}/fastapi_access.log combined
</VirtualHost>
Activate the configuration and disable the default site:
sudo a2ensite fastapi.conf
sudo a2dissite 000-default.conf
sudo systemctl reload apache2
At this point Apache listens on port 80 and proxies every request to the Uvicorn process managed by systemd. Verify the setup with curl -I http://api.example.com/. A 200 OK response confirms the reverse proxy is operational. If you need HTTPS, replace the <VirtualHost :80> block with <VirtualHost :443> and add the usual SSLEngine on directives, then reload Apache again.
After placing fastapi.service in /etc/systemd/system, tell systemd to read the new file and then bring the unit up. The sequence is deliberately simple so you can repeat it on any server without hunting for hidden steps.
# Reload unit files so systemd sees the new service definition
sudo systemctl daemon-reload
# Enable the service to start automatically at boot
sudo systemctl enable fastapi.service
# Start it right now
sudo systemctl start fastapi.service
# Verify it’s running and listening on the socket you defined
sudo systemctl status fastapi.service
If the service fails to start, journalctl -u fastapi.service -b will show the most recent logs, which is invaluable during the first deployment. A common pitfall is an incorrect WorkingDirectory or a missing ExecStart path; double‑check that the virtual‑environment activation script is referenced explicitly, e.g. ExecStart=/opt/fastapi-demo/venv/bin/uvicorn main:app --uds /run/fastapi.sock.
For production stability add a restart policy in the unit file:
[Service]
Restart=on-failure
RestartSec=5
With the service enabled and started, the socket file appears under /run/fastapi.sock. Apache’s reverse‑proxy configuration can now forward HTTP traffic to this Unix socket, completing the deployment pipeline.
After the services are up, verify the end‑to‑end flow with a simple request against the public URL.
# Replace example.com with your domain or IP
curl -I http://example.com/health
A 200 OK and the JSON payload you defined in the FastAPI route confirm that Apache successfully proxied to Uvicorn. If the request hangs or returns 502 Bad Gateway, start digging with the service managers.
# Check that the systemd unit is active
systemctl status fastapi.service
# Show the last lines of the app’s journal
journalctl -u fastapi.service -n 20 --no-pager
Typical failures:
| Symptom | Likely cause | Quick fix |
|---|---|---|
502 Bad Gateway | Apache cannot reach the Unix socket | Ensure the socket path in fastapi.service matches the one in fastapi.conf; set chmod 660 and chown to the fastapi user. |
Permission denied (socket) | Socket owned by root or wrong group | Add Group=fastapi and User=fastapi in the service file, then systemctl daemon-reload && systemctl restart fastapi. |
No response from /health | Uvicorn process crashed or never started | Look for traceback in journalctl; missing dependencies in the venv are a common culprit—activate the venv and run uvicorn app:app manually. |
| Apache returns 404 | VirtualHost not enabled or mis‑named | a2ensite fastapi.conf && systemctl reload apache2. |
sudo ufw status | grep '80\|443'
If the ports are blocked, open them with ufw allow http and ufw allow https. Once all checks pass, the deployment is ready for production traffic.
Create a unit file in /etc/systemd/system, e.g., fastapi.service. Set Description, After=network.target, and specify User and Group. In the [Service] section, use ExecStart=/usr/bin/uvicorn main:app --host 127.0.0.1 --port 8000 --workers 4 and set Restart=on-failure. Optionally add EnvironmentFile=/etc/default/fastapi for env vars. Finally, reload systemd (systemctl daemon-reload), enable the service (systemctl enable fastapi), and start it (systemctl start fastapi). Check status with systemctl status fastapi.
Enable proxy modules: a2enmod proxy proxy_http. Then create a VirtualHost (e.g., /etc/apache2/sites-available/fastapi.conf) with ServerName yourdomain.com. Inside, add ProxyPreserveHost On, ProxyPass / http://127.0.0.1:8000/, and ProxyPassReverse / http://127.0.0.1:8000/. Optionally set RequestHeader set X-Forwarded-Proto "https" for HTTPS. Enable the site (a2ensite fastapi) and reload Apache (systemctl reload apache2). This forwards all incoming traffic to the FastAPI process managed by systemd.
Obtain an SSL certificate (Let's Encrypt via certbot is common). After installing certbot, run certbot --apache -d yourdomain.com. Certbot will configure the <VirtualHost *:443> block with SSLCertificateFile and SSLCertificateKeyFile directives. Keep the ProxyPass and ProxyPassReverse lines inside this block. Ensure SSLProxyEngine on is set to allow Apache to forward HTTPS traffic to the backend. Restart Apache to apply changes. The client connections are encrypted, while the internal proxy to FastAPI can remain HTTP.
Typical variables include APP_ENV=production, LOG_LEVEL=info, and any secret keys (e.g., DATABASE_URL, REDIS_URL). Place them in a separate file like /etc/default/fastapi, each on its own line (KEY=value). In the service file, add EnvironmentFile=/etc/default/fastapi under the [Service] section. This keeps credentials out of the unit file and allows easy updates without editing the service definition. Remember to secure the file permissions (chmod 640) and restrict ownership to root.
First, verify the FastAPI process is listening on the expected port: sudo netstat -tulpn | grep 8000 or ss -ltnp. Check systemd logs (journalctl -u fastapi -f) for errors. Ensure the ExecStart command matches the actual entry point (module:app). Confirm Apache's proxy modules are enabled and the VirtualHost points to the correct IP/port. Look at Apache error logs (/var/log/apache2/error.log) for connection refusals. If SELinux/AppArmor is active, ensure it permits Apache to connect to the local port.