API keys, database passwords, SMTP credentials, license keys, third-party webhooks… almost every modern application running on a VPS depends on sensitive configuration values. Most teams put these values into .env files or environment variables, but the way those secrets are stored on the server often gets far less attention than the application code itself. As a result, we routinely see projects where .env files are world-readable, committed to Git, sitting inside the public web root, or sprinkled across backup archives without any plan. In this guide, we will walk through practical, real-world patterns for managing .env files and secrets safely on a VPS, based on what we do and review with our own customers at dchost.com.
We will focus on Linux VPS servers (Ubuntu, Debian, AlmaLinux, etc.) and common stacks like PHP/Laravel, WordPress and Node.js, but the principles apply broadly. You will learn how to structure .env files, where to store them on disk, which permissions to use, how to integrate with systemd, how to handle CI/CD and backups, and when it makes sense to move beyond plain .env files to encrypted secret management.
İçindekiler
- 1 What .env Files and Secrets Really Are (and Why They Matter on a VPS)
- 2 Common .env Mistakes We Keep Seeing on VPS Servers
- 3 Designing a Secure .env Layout on a VPS
- 4 Step-by-Step: Locking Down .env on an Ubuntu/Debian VPS
- 5 Framework-Specific Notes: Laravel, WordPress and Node.js
- 6 Git, CI/CD and Secrets: What Belongs in the Repository?
- 7 Advanced Secret Management on a VPS: Encryption and Rotation
- 8 Backups, Snapshots and .env Files
- 9 When to Move Beyond .env: Dedicated Secret Managers
- 10 Putting It All Together on a dchost.com VPS
What .env Files and Secrets Really Are (and Why They Matter on a VPS)
In most frameworks, a .env file is a simple text file containing key–value pairs, for example:
APP_ENV=production APP_DEBUG=false DB_HOST=127.0.0.1 DB_DATABASE=myapp DB_USERNAME=myapp_user DB_PASSWORD=<very-secret> MAIL_HOST=smtp.example.com MAIL_PASSWORD=<another-secret>
These values are the secrets that unlock your infrastructure:
- Database usernames and passwords
- Redis and queue passwords
- API tokens for payment gateways, SMS, shipping and maps
- OAuth client IDs/secrets for sign-in providers
- Encryption keys, JWT signing keys, webhook secrets
Whether you store them in a .env file or as process environment variables, anyone who can read these values can often:
- Dump your entire customer database
- Send email or SMS in your name
- Issue refunds or charge cards via payment APIs
- Access internal admin panels or private APIs
On a VPS you control the whole operating system, which is powerful but also risky: a single misconfigured folder or backup job can expose everything. That is why secrets must be treated at least as carefully as your source code, and in many cases even more carefully.
Common .env Mistakes We Keep Seeing on VPS Servers
Before we talk about good practices, it helps to recognize the patterns that cause trouble again and again. In our VPS security reviews and setups at dchost.com, we see a few recurring issues.
1. .env Files Inside the Public Web Root
Example: Laravel installed under /var/www/html and the .env file sits right next to index.php, with a web server rule that does not block access to dotfiles. A misconfiguration, an added alias, or an Nginx rewrite mistake later, and https://example.com/.env suddenly returns your entire secret set as plain text.
Even if your current configuration blocks dotfiles, putting .env under the public root increases the blast radius if someone changes vhost rules, adds a static file alias, or migrates to a different server setup.
2. .env Committed to Git Repositories
Another classic: .env not in .gitignore, pushed to a remote repository along with application code. Even in a private repo, this is dangerous:
- Multiple developers and services gain access to live production secrets
- Old branches and forks preserve secrets long after they should be rotated
- Leaked Git backups or misconfigured Git HTTP access expose everything
Once a secret hits Git history, you must assume it is compromised and rotate it.
3. Overly Permissive File Permissions
We frequently see .env files with permissions like 644 or even 664, owned by root or a generic user that many system processes can impersonate. That means any local user account, misbehaving script, PHP process under a different pool, or compromised site on the same server can read your secrets.
.env files should typically be readable only by the user account that runs the application process (or by a dedicated configuration user) — no one else.
4. Secrets Leaking into Logs and Debug Pages
Debugging output that dumps entire configuration arrays, or error logs that print full exception traces including connection strings, are a silent but very real leak channel. If you have not yet tuned your PHP and web server logging, it is worth reading our guide on PHP error logging best practices on hosting servers to avoid accidentally recording sensitive configuration values.
5. Backups and Snapshots Without a Secret Strategy
Backups that include .env files are necessary, but if those backups are copied to unencrypted storage, e-mailed as archives, or synced to a shared location without access control, your secrets travel much further than intended. We will come back to backup strategy later, because .env security does not stop at the VPS filesystem.
Designing a Secure .env Layout on a VPS
Let’s walk through a clean, repeatable layout you can use on almost any Linux VPS for a typical web application.
1. Separate Code from Configuration
As a general rule:
- Code (Git repository) goes under something like
/var/www/myapp/releases/... - Configuration (including .env) goes under something like
/etc/myapp/or/var/www/myapp/shared/
Your deployment process then symlinks or points the runtime to the right configuration location. This matches the 12-factor app principle: build once, configure per environment.
2. Place .env Outside the Public Web Root
For a PHP or Node.js app served via Nginx/Apache, your public root is usually something like:
/var/www/myapp/current/public(Laravel)/var/www/myapp/current/public_html(classic PHP)/var/www/myapp/current/dist(SPA/static bundle)
A good pattern is:
- Public web root:
/var/www/myapp/current/public - Application code:
/var/www/myapp/current - Shared config:
/var/www/myapp/shared/.env(or/etc/myapp/myapp.env)
The application process or framework is configured to read the .env from the shared path, never from under the public root.
3. Use a Dedicated Unix User
On a VPS it is tempting to run everything as root or www-data. A safer approach:
- Create a dedicated user for each major application, e.g.
myapp - Run PHP-FPM pool, Node.js process, or queue workers as
myapp - Make
myappthe owner of the .env file
This way, even if another site on the same VPS is compromised, it is harder for an attacker to read your myapp configuration files.
4. Lock Down File Permissions Correctly
For a single-tenant app, a good baseline is:
sudo chown myapp:myapp /var/www/myapp/shared/.env sudo chmod 600 /var/www/myapp/shared/.env
600 means: readable and writable only by owner; no group or other permissions. If you must let the web server user read the file (e.g. www-data), you can set:
sudo chown myapp:www-data /var/www/myapp/shared/.env sudo chmod 640 /var/www/myapp/shared/.env
Then configure PHP-FPM/Node to run under the myapp user, and Nginx/Apache workers that never need to read the .env keep running as www-data.
5. Decide: Environment File vs Native Environment Variables
There are two common patterns:
- Environment file: a .env file loaded by a library like
vlucas/phpdotenv(Laravel), or thedotenvpackage in Node.js - Native environment variables: systemd
Environment=orEnvironmentFile=entries that you read directly viagetenv()/$_ENV/process.env
Both are valid. For most existing projects, keeping a .env file and having systemd or a dotenv library load it is the least disruptive path. In more advanced setups, you might prefer to store secrets in a secure store and inject them directly as environment variables via systemd, without a .env file on disk at all.
Step-by-Step: Locking Down .env on an Ubuntu/Debian VPS
Let’s outline a concrete flow for a typical app deployed to an Ubuntu or Debian VPS. Adjust paths and usernames as needed.
1. Create an Application User
sudo adduser --system --group --home /var/www/myapp myapp
This creates a system user and group called myapp with home directory /var/www/myapp.
sudo mkdir -p /var/www/myapp/shared sudo chown myapp:myapp /var/www/myapp/shared sudo chmod 750 /var/www/myapp/shared
The shared directory will hold .env and possibly other non-public configuration files.
3. Create the .env File Safely
You can either create the .env file locally and upload via SFTP as the myapp user, or create it directly on the VPS:
sudo -u myapp nano /var/www/myapp/shared/.env
Paste your key–value pairs, save and exit. Then lock permissions:
sudo chown myapp:myapp /var/www/myapp/shared/.env sudo chmod 600 /var/www/myapp/shared/.env
4. Configure Your Application to Use This Path
For Laravel, you can set the APP_ENV_FILE path via bootstrap or use a small wrapper to pass the correct file to phpdotenv. For Node.js with dotenv:
require('dotenv').config({
path: '/var/www/myapp/shared/.env'
});
For custom PHP apps, you can write a tiny loader at the start of config.php that parses /var/www/myapp/shared/.env and populates $_ENV or constants.
5. Use systemd to Run the App Under the Right User
If you manage your app with systemd (recommended on a VPS), a sample service unit might look like:
[Unit] Description=MyApp PHP-FPM Worker After=network.target [Service] User=myapp Group=myapp WorkingDirectory=/var/www/myapp/current EnvironmentFile=/var/www/myapp/shared/.env ExecStart=/usr/bin/php artisan queue:work --sleep=3 --tries=3 Restart=always RestartSec=5 [Install] WantedBy=multi-user.target
Here, systemd loads environment variables from your .env file before launching the process. This works well for queue workers, schedulers and custom daemons. For PHP-FPM or Nginx themselves, you typically reference the .env inside the app bootstrap or framework instead.
If you are new to systemd and process management on a VPS, it is worth pairing this with our VPS security hardening checklist, which also covers safe service configuration and user isolation.
Framework-Specific Notes: Laravel, WordPress and Node.js
Different stacks have slightly different expectations around .env handling. Let’s look at a few common ones.
Laravel and Other Modern PHP Frameworks
Laravel (and many similar frameworks) uses .env heavily. Key points:
- Make sure
.envis not inside the document root (publicdirectory). - Always add
/.envto.gitignore. - Commit a
.env.examplefile to show required keys without real values. - In production, consider using
php artisan config:cacheso your app does not read .env on every request.
If you are tuning PHP-FPM and OPcache for performance at the same time, you can combine this work with the recommendations in our guide on PHP OPcache settings for WordPress, Laravel and WooCommerce.
WordPress (wp-config.php + SALTs)
WordPress does not use a .env file by default, but the same secret management issues apply. Database credentials, AUTH/SECURE salts and keys, and SMTP settings often live in wp-config.php. Better patterns include:
- Move
wp-config.phpone level above the web root if possible. - Load sensitive values from environment variables set by systemd or an external file.
- Keep file permissions tight (e.g.
640owned by a dedicated user).
For a broader look at tightening WordPress, see our WordPress security checklist on shared hosting — the principles carry over directly to VPS environments as well.
Node.js Applications
Most Node apps use the dotenv package in development, and often production as well. Best practices:
- Use an explicit path:
dotenv.config({ path: '/var/www/myapp/shared/.env' }) - Never commit
.env; provide.env.exampleinstead. - For containers, prefer native environment variables injected at runtime.
When you run Node.js in production on a VPS under systemd, you can either let systemd load the .env via EnvironmentFile or read it from the filesystem inside your Node code. Both are valid; pick one approach and standardize across your projects.
Git, CI/CD and Secrets: What Belongs in the Repository?
A lot of .env trouble starts from confusing what lives in Git vs what lives only on servers. A simple rule of thumb:
- In Git:
.env.example,.env.testing, maybe.env.localfor a fully local stack without real third-party accounts. - Never in Git: live production database passwords, real payment API keys, live SMTP credentials, long-term encryption keys.
Using .env.example Correctly
Your .env.example file should:
- List all required keys
- Contain placeholder or obviously fake values
- Be safe to publish publicly (assume it can leak)
This file becomes documentation for future team members and for CI/CD pipelines that need to know which variables to inject.
Handling Secrets in CI/CD Pipelines
Modern CI/CD tools often include a secure secrets store. The usual pattern is:
- Store production and staging secrets in the CI/CD system’s encrypted variables.
- On deploy, the pipeline connects to your VPS and writes or updates the .env file from those variables.
- The pipeline reloads or restarts the application via systemd with zero or minimal downtime.
We show this style of deployment — including how to use rsync, symlinked release directories, and systemd restarts without downtime — in our guide on zero‑downtime CI/CD to a VPS. Combine that deployment pattern with the secret-handling rules here and you get a very robust pipeline.
Key CI/CD rules:
- Never print secrets in pipeline logs (no
echo $DB_PASSWORD). - Restrict who can see pipeline variables.
- Rotate CI/CD credentials regularly and revoke unused tokens.
Advanced Secret Management on a VPS: Encryption and Rotation
Plaintext .env files are simple and work well if you control access to the VPS and keep backups secure. As projects and teams grow, you may want stronger guarantees: encrypted-at-rest configuration, auditable updates and painless rotation.
Encrypted .env Files with sops + age
One practical pattern is to store an encrypted version of your .env in Git, and decrypt it only on the VPS at deploy time. A typical stack uses:
You commit .env.sops.yaml (encrypted) to Git, not the plaintext .env. Your deploy script, running on the VPS, decrypts this file into /var/www/myapp/shared/.env using a deployment key stored only on that server.
We have an entire, hands-on playbook about this approach in our article The Calm Way to Secrets on a VPS: GitOps with sops + age, systemd and rotation. If you want Git-based configuration and reproducible deployments without exposing secrets, that article is a natural next step.
Secret Rotation Policy
Regardless of tooling, you need a plan for rotation — changing secrets on a schedule or after incidents:
- Database passwords rotated every few months (or faster if there is any suspicion of leakage).
- API keys regenerated when staff change, or when third-party providers indicate a risk.
- JWT and encryption keys approached carefully: rotation strategies must consider active sessions and data format.
On a VPS, rotation is typically implemented as a small script plus a scheduled job. Our guide on Linux crontab best practices walks through how to schedule tasks safely, including logging and failure alerts.
Backups, Snapshots and .env Files
Secrets are not safe if they only live in one place. But including them in backups without planning can increase your risk. On a VPS, you usually have three layers:
- Application-level backups (database dumps, file archives)
- Server-level backups (filesystem snapshots, rsync backups)
- Off-site backups (object storage, another data center)
What to Include
In most cases, you should back up your .env files because they contain operational knowledge that can be hard to reconstruct under stress. However:
- Ensure your backup buckets or remote servers are protected by strong authentication.
- Encrypt backups at rest (e.g. using restic or Borg with strong passphrases).
- Limit access to backup storage accounts as tightly as production.
If you are designing a full backup strategy for your stack, we recommend pairing this with our guide to the 3‑2‑1 backup strategy and automating backups on VPS servers, which also covers off-site storage patterns.
Masking Secrets in Logs and Monitoring
Many modern frameworks and loggers support secret masking, replacing values that look like API keys or passwords with *** in logs. Enable these features where available. Reviewing logs and metrics from your dchost.com VPS should help you diagnose issues — not leak credentials.
When to Move Beyond .env: Dedicated Secret Managers
For many small and medium projects, well-managed .env files on a properly secured VPS are enough. But there are cases where you might want a dedicated secret management system:
- Multiple services and microservices sharing secrets
- Strict compliance environments (PCI DSS, banking, healthcare)
- Large teams with fine-grained access requirements and audit needs
Self-hosted tools like HashiCorp Vault or open-source alternatives can run on a dedicated dchost.com VPS or dedicated server. In that model, your applications obtain short-lived secrets via an authenticated API call at startup, and long-term master keys live only in the secret manager. This is more complex to set up and operate, but it can significantly reduce the impact of a single compromised host.
Putting It All Together on a dchost.com VPS
Managing .env files and secrets securely on a VPS is not about fancy tools first; it starts with a few disciplined habits:
- Keep .env out of the public web root and out of Git history.
- Use dedicated Unix users and correct permissions (600/640) for configuration files.
- Use systemd to run your services under the right user with explicit EnvironmentFile entries where appropriate.
- Plan CI/CD flows so that secrets are injected at deploy time from secure variables, not hard-coded in scripts.
- Back up .env files as part of a secure, encrypted 3‑2‑1 backup strategy, with regular rotation of sensitive keys.
From our side at dchost.com, we see best results when customers treat secrets as part of their overall VPS architecture, not an afterthought. When you combine the patterns in this guide with a hardened base server (SSH security, firewalls, Fail2ban, timely updates) and good monitoring, your risk profile drops dramatically. Our articles on VPS security hardening and VPS monitoring and alerts are natural companions to this one.
If you are planning a new project or want to review an existing one, our VPS, dedicated server and colocation options give you the control you need to implement all these practices cleanly. Start with a VPS plan that fits your CPU, RAM and storage needs, structure your deployments and .env management as described, and you will have a foundation that scales from prototype to production without having to re-think how you handle secrets later.
