On this page
One server still needs clear boundaries
Running a small website on one server is reasonable. The problem is not the number of machines; it is whether the reverse proxy, application, database, and backups have distinct roles. If every process runs as root, the database is open to the internet, and application code sits beside user uploads, the first incident becomes a search through everything at once.
You do not need a diagram full of cloud-provider icons. A useful deployment sketch answers five questions: Where does a request enter? Which user runs each process? Where is persistent data stored? Where are backups kept? What symptom appears when each layer fails?
Draw the request path from the outside in
A typical path starts with DNS pointing a domain to the server's public IP. The firewall exposes only 80/443 and controlled SSH access. Caddy accepts HTTP and HTTPS, then forwards requests to an application bound to a loopback address such as 127.0.0.1:3000. The database accepts only local or private-container connections; it does not need a public 5432 or 3306 port.
Internet -> 80/443 -> Caddy -> 127.0.0.1:3000 -> Application
|-> Database
|-> Uploaded filesThis path is also a troubleshooting map. A wrong destination starts with DNS. Certificate and some HTTP errors lead you to Caddy. For a 502, check the upstream address and whether the application is running. Investigate database or business-logic errors after the request has reached the application.
Give each component only the access it needs
Use separate system users for Caddy, the application, and the database. Application code can be read-only at runtime; grant write access only to the directories used for logs, uploads, or temporary data. A deployment account may use sudo to manage services, but normal application processes should not run permanently as that account or as root.
Restrict environment-file access to the users that need it. Do not place .env in a public static directory or print complete environment variables to startup logs. Make directory roles obvious: for example, /opt/myapp/releases for code, /var/lib/myapp/uploads for uploads, and /var/backups/myapp for backups, instead of putting everything under /root/app.
Separate durable data from replaceable files
Code, dependencies, and build output should be reproducible from a repository or image. Databases, user uploads, and secrets are different: losing them may be irreversible. Mark each persistent store on the diagram and document how to back it up and restore it. A container volume is not a backup, and a disk snapshot does not automatically replace a consistent application-level backup.
Logs can be regenerated, but keep enough history for incident investigation. Set rotation and disk limits so one noisy failure cannot fill the root filesystem. Temporary files belong in a known location with a safe cleanup process.
Keep a port table, not just a firewall note
| Address or port | Used by | Public? | Purpose |
|---|---|---|---|
22/tcp | Administrators | Restricted | SSH; limit source addresses where practical |
80/443 | Caddy | Yes | The website's public entry point |
127.0.0.1:3000 | Application | No | Reached through the reverse proxy |
127.0.0.1:5432 | Database | No | Reached by local application processes |
Your exact ports may differ. The principle is a small public surface and explicit internal addresses. Use ss -lntp to check what is actually listening; configuration files alone are not proof.
Accept and plan for single-server failure
If the one machine fails, the proxy, application, and database all stop. Disk damage can affect every local copy. For a low-traffic content site, off-site backups, automatic startup, monitoring, and a clear recovery guide can make that risk manageable. If the business needs availability across data centers, do not present a single server as a fault-tolerant design.
Before launch, decide how much data you can afford to lose, how long the site may be unavailable, and who will perform recovery. Those answers should drive backup frequency and whether a second machine is warranted—not the other way around.
What counts as done?
Keep four simple records: a request path, a process-and-user list, a port table, and a persistent-data-and-backup list. Then move the application from a temporary terminal session into a systemd service so it can start automatically, run with limited privileges, and leave searchable logs.
Sources
- Caddy reverse_proxy directiveCaddy Documentation
Next steps
Continue with the next useful task; you do not need to read everything at once.