Shubham
07/07/2026, 4:22 PMMarvin
07/07/2026, 4:22 PMMarvin
07/07/2026, 4:24 PMEnvironment="PREFECT_API_DATABASE_CONNECTION_URL=<postgresql+asyncpg://prefect:prefectpassword@127.0.0.1:5432/prefect>"
not:
<postgresql+asyncpg://...>
You’re missing database migrations
Before first start, run:
bash
prefect server database upgrade -y
Without this, the server may start against an uninitialized DB.
PREFECT_SERVER_API_AUTH_STRING is valid in Prefect 3.x
That setting is supported for basic auth, in user:password form.
Don’t set host twice unless you mean to
You have both:
- Environment="PREFECT_SERVER_API_HOST=x.x.x.x"
- ExecStart=... --host 0.0.0.0
The CLI flag wins. Usually just keep:
bash
--host 0.0.0.0
and remove PREFECT_SERVER_API_HOST.
Running as root is not recommended
Use a dedicated service user if possible.
Your service file name/creation step is missing
You showed the unit contents, but not where it gets written. Typically:
bash
sudo nano /etc/systemd/system/prefect-server.service
or write it with tee.
Here’s a corrected version of your setup.
Install + DB setup
bash
pip install -U prefect
mkdir -p /home/ubuntu/.prefect
sudo apt update
sudo apt install -y postgresql postgresql-contrib
sudo -u postgres psql
Then in psql:
sql
CREATE DATABASE prefect;
CREATE USER prefect WITH PASSWORD 'prefectpassword';
GRANT ALL PRIVILEGES ON DATABASE prefect TO prefect;
\q
Run migrations once
bash
prefect server database upgrade -y
Systemd unit
Put this in `/etc/systemd/system/prefect-server.service`:
ini
[Unit]
Description=Prefect Server
After=network-online.target postgresql.service
Wants=network-online.target
[Service]
Type=simple
User=ubuntu
WorkingDirectory=/home/ubuntu
Environment="PREFECT_HOME=/home/ubuntu/.prefect"
Environment="PREFECT_SERVER_API_PORT=4200"
Environment="PREFECT_SERVER_API_KEEPALIVE_TIMEOUT=60"
Environment="PREFECT_SERVER_LOGGING_LEVEL=DEBUG"
Environment="PREFECT_API_DATABASE_CONNECTION_URL=<postgresql+asyncpg://prefect:prefectpassword@127.0.0.1:5432/prefect>"
Environment="PREFECT_SERVER_API_AUTH_STRING=xxx:xxxx"
ExecStart=/usr/local/bin/prefect server start --host 0.0.0.0 --port 4200 --keep-alive-timeout 60
Restart=on-failure
RestartSec=10
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
Then:
bash
sudo systemctl daemon-reload
sudo systemctl enable prefect-server
sudo systemctl restart prefect-server
sudo systemctl status prefect-server
Optional but recommended checks
bash
journalctl -u prefect-server -f
A couple more production notes:
- If prefect was installed in a venv, /usr/local/bin/prefect may be wrong; use which prefect
- Storing DB password and auth string directly in the unit file works, but an environment file is safer
- If this server is internet-facing, put it behind TLS/reverse proxy; basic auth alone is not enough
Verified CLI:
bash
prefect server start --help
Useful docs:
- Prefect docs
- Server CLI guide
If you want, I can also rewrite this into a best-practice Ubuntu systemd setup with:
- dedicated prefect user
- /etc/prefect/server.env
- automatic startup
- reverse proxy example with NginxShubham
07/07/2026, 4:28 PMMarvin
07/07/2026, 4:28 PMMarvin
07/07/2026, 4:31 PM127.0.0.1:4200 only
- Put Nginx in front of it for:
- TLS/HTTPS
- optional IP allowlisting
- rate limiting
- optional SSO / OAuth2 / OIDC via auth proxy
- Use PostgreSQL for the DB
- Run Prefect as a dedicated non-root prefect user
- Store secrets in /etc/prefect/server.env with restricted permissions
Important note: Prefect server itself is not where you should terminate TLS. Best practice is a reverse proxy in front of it.
---
1) Install system packages
bash
sudo apt update
sudo apt install -y python3-venv python3-pip postgresql postgresql-contrib nginx
If you want Let’s Encrypt later:
bash
sudo apt install -y certbot python3-certbot-nginx
---
2) Create a dedicated service user
bash
sudo useradd --system --create-home --home-dir /var/lib/prefect --shell /usr/sbin/nologin prefect
Create directories:
bash
sudo mkdir -p /opt/prefect
sudo mkdir -p /etc/prefect
sudo mkdir -p /var/lib/prefect/.prefect
sudo chown -R prefect:prefect /var/lib/prefect
sudo chmod 700 /var/lib/prefect/.prefect
---
3) Create a Python virtual environment and install Prefect
bash
sudo python3 -m venv /opt/prefect/venv
sudo /opt/prefect/venv/bin/pip install --upgrade pip
sudo /opt/prefect/venv/bin/pip install -U prefect
Check the binary:
bash
/opt/prefect/venv/bin/prefect version
---
4) Configure PostgreSQL
Open psql:
bash
sudo -u postgres psql
Run:
sql
CREATE USER prefect WITH PASSWORD 'change_this_to_a_strong_password';
CREATE DATABASE prefect OWNER prefect;
GRANT ALL PRIVILEGES ON DATABASE prefect TO prefect;
\q
---
5) Create the Prefect environment file
Create `/etc/prefect/server.env`:
bash
sudo tee /etc/prefect/server.env > /dev/null <<'EOF'
PREFECT_HOME=/var/lib/prefect/.prefect
# Bind Prefect only to localhost; Nginx will expose it publicly
PREFECT_SERVER_API_HOST=127.0.0.1
PREFECT_SERVER_API_PORT=4200
PREFECT_SERVER_API_KEEPALIVE_TIMEOUT=60
PREFECT_SERVER_LOGGING_LEVEL=INFO
# PostgreSQL
PREFECT_API_DATABASE_CONNECTION_URL=<postgresql+asyncpg://prefect:change_this_to_a_strong_password@127.0.0.1:5432/prefect>
# Optional basic auth
PREFECT_SERVER_API_AUTH_STRING=admin:change_this_too
# Recommended security settings
PREFECT_SERVER_API_CSRF_PROTECTION_ENABLED=true
# Restrict browser origins to your actual URL
PREFECT_SERVER_API_CORS_ALLOWED_ORIGINS=["<https://prefect.example.com>"]
# Make the UI call the external URL, not localhost
PREFECT_UI_API_URL=<https://prefect.example.com/api>
EOF
Then lock it down:
bash
sudo chown root:prefect /etc/prefect/server.env
sudo chmod 640 /etc/prefect/server.env
Notes
- Use a real hostname in PREFECT_UI_API_URL
- The database URL must use postgresql+asyncpg://
- PREFECT_SERVER_API_AUTH_STRING is valid, but don’t rely on it alone for internet exposure
---
6) Run database migrations once
bash
sudo env $(cat /etc/prefect/server.env | xargs) /opt/prefect/venv/bin/prefect server database upgrade -y
If your env file ever contains spaces/special chars, a safer pattern is:
bash
sudo bash -c 'set -a; source /etc/prefect/server.env; set +a; /opt/prefect/venv/bin/prefect server database upgrade -y'
---
7) Create the systemd service
Create `/etc/systemd/system/prefect-server.service`:
```ini
[Unit]
Description=Prefect Server
After=network-online.target postgresql.service
Wants=network-online.target
[Service]
Type=simple
User=prefect
Group=prefect
WorkingDirectory=/var/lib/prefect
EnvironmentFile=/etc/prefect/server.env
ExecStart=/opt/prefect/venv/bin/prefect server start --host 127.0.0.1 --port 4200 --keep-alive-timeout 60
Restart=on-failure
RestartSec=10
StandardOutput=journal
StandardError=journal
# Basic hardening
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=full
ProtectHome=trueMarvin
07/07/2026, 4:31 PMThen enable/start:
```bash
sudo systemctl daemon-reload
sudo systemctl enable prefect-server
sudo systemctl start prefect-server
sudo systemctl status prefect-server
Logs:
bash
sudo journalctl -u prefect-server -f
---
8) Configure Nginx as a reverse proxy
Create `/etc/nginx/sites-available/prefect`:
nginx
server {
listen 80;
server_name <http://prefect.example.com|prefect.example.com>;
location / {
proxy_pass <http://127.0.0.1:4200;>
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 90;
proxy_connect_timeout 90;
proxy_send_timeout 90;
}
}
Enable it:
bash
sudo ln -s /etc/nginx/sites-available/prefect /etc/nginx/sites-enabled/prefect
sudo nginx -t
sudo systemctl reload nginx
---
9) Add HTTPS
If DNS already points to this box:
bash
sudo certbot --nginx -d <http://prefect.example.com|prefect.example.com>
That gives you TLS. This is the minimum for safe exposure.
---
How to protect it beyond basic auth
Best options, in order of strength:
Option A: Restrict access at the network layer
If only your team or internal workers need access, do this first:
- bind Prefect to localhost
- expose only through VPN, private subnet, bastion, or corporate network
- optionally allowlist IPs in Nginx
Example Nginx allowlist:
nginx
location / {
allow 203.0.113.10;
allow 203.0.113.11;
deny all;
proxy_pass <http://127.0.0.1:4200;>
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
This is much stronger than basic auth alone for many self-hosted setups.
---
Option B: Put SSO/OIDC in front with an auth proxy
Good choices:
- oauth2-proxy
- Nginx + auth_request
- Cloudflare Access
- Tailscale Funnel / Tailscale access controls
- your cloud LB with OIDC auth
This is the most common “real” upgrade beyond basic auth.
Typical pattern:
- user hits <https://prefect.example.com>
- auth proxy forces login with Google, Okta, Azure AD, GitHub, etc.
- only authenticated users can reach Prefect
- Prefect itself remains on localhost
This is usually better than exposing Prefect directly with just PREFECT_SERVER_API_AUTH_STRING.
---
Option C: Keep basic auth, but only behind TLS + rate limiting
If you must use basic auth:
- enable HTTPS
- add rate limiting in Nginx
- use strong random passwords
- rotate credentials
- consider IP allowlist too
Example rate limiting:
nginx
limit_req_zone $binary_remote_addr zone=prefect_limit:10m rate=10r/s;
server {
listen 443 ssl http2;
server_name <http://prefect.example.com|prefect.example.com>;
location / {
limit_req zone=prefect_limit burst=20 nodelay;
proxy_pass <http://127.0.0.1:4200;>
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
---
Option D: Put it behind a private ingress only
If flows/workers are inside your VPC:
- no public DNS
- internal load balancer only
- security groups/firewall rules
- VPN for human access
For many teams, this is the cleanest secure deployment.
---
Recommended security baseline
I’d suggest this baseline:
1. PostgreSQL backend
2. Dedicated prefect Linux user
3. Virtualenv install in /opt/prefect/venv
4. Prefect bound to 127.0.0.1 only
5. Nginx reverse proxy with HTTPS
6. CSRF enabled
7. Restricted CORS origins
8. Either
- VPN/private network access, or
- SSO/OIDC auth proxy
9. Optional basic auth as an extra layer, not the only one
---
Client/worker configuration
Your workers/clients should point to:
```bashMarvin
07/07/2026, 4:31 PMIf you keep server basic auth enabled:
```bash
export PREFECT_API_AUTH_STRING=admin:change_this_too
---
Things I would change from many “quick install” examples
- don’t run as root
- don’t expose 0.0.0.0:4200 directly unless you must
- don’t skip TLS
- don’t keep secrets in a world-readable unit file
- don’t rely only on basic auth for public internet exposure
---
Useful docs:
- Prefect docs
- Security settings
- Self-hosted Prefect
If you want, I can next give you either:
1. a copy-paste complete setup for <http://prefect.example.com|prefect.example.com> with Nginx + Let’s Encrypt, or
2. an SSO-protected setup using oauth2-proxy in front of Prefect.Bring your towel and join one of the fastest growing data communities. Welcome to our second-generation open source orchestration platform, a completely rethought approach to dataflow automation.
Powered by