Technology

Managing .env Files and Secrets on a VPS Safely

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

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 myapp the 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 the dotenv package in Node.js
  • Native environment variables: systemd Environment= or EnvironmentFile= entries that you read directly via getenv() / $_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.

2. Create a Shared Configuration Directory

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 .env is not inside the document root (public directory).
  • Always add /.env to .gitignore.
  • Commit a .env.example file to show required keys without real values.
  • In production, consider using php artisan config:cache so 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.php one 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. 640 owned 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.example instead.
  • 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.local for 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:

  1. Store production and staging secrets in the CI/CD system’s encrypted variables.
  2. On deploy, the pipeline connects to your VPS and writes or updates the .env file from those variables.
  3. 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:

  • sops for structured secret management
  • age for modern encryption keys

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.

Frequently Asked Questions

No. Even in a private repository, you should not commit real production .env files. Private repos can still be cloned to laptops, forked, backed up in unsafe ways, or accidentally made public later. Instead, commit a .env.example file that lists all the required keys with obviously fake placeholder values. Keep the real .env file only on your VPS and in your secure CI/CD secrets store. If a real .env has ever been committed, assume those secrets are compromised and rotate them.

A good pattern is to keep your .env file outside the public web root and under a directory owned by a dedicated application user. For example, use /var/www/myapp/shared/.env or /etc/myapp/myapp.env instead of placing .env next to index.php in the public folder. Set ownership to the app user (e.g. myapp:myapp) and restrict permissions to 600 or 640. Then configure your framework or systemd service to load the file from that location, rather than relying on default paths.

Use your CI/CD system’s encrypted variable or secrets feature. Store database passwords, API keys and other sensitive values there, not in the Git repository. During deployment, the pipeline connects to your VPS and writes or updates the .env file from these variables, or injects them as native environment variables into systemd units. Avoid printing secrets in logs and restrict who can view or edit pipeline variables. For a full deployment pattern, combine this with a zero‑downtime strategy like the one we describe in our guide to CI/CD for VPS servers.

Rotation frequency depends on your risk profile, but it is wise to rotate critical secrets such as database passwords and high-privilege API keys every few months, and immediately after any suspected incident. For OAuth or external APIs, check the provider’s guidance as some offer short-lived tokens that reduce the need for manual rotation. Treat rotation like any other maintenance task: script the change, update the .env file via a controlled process, restart or reload services, and verify that applications still work before deleting old credentials.

For many small and medium projects, well-managed .env files on a hardened VPS are sufficient, especially if you control SSH access, lock file permissions and secure your backups. A dedicated secret manager (like Vault) becomes more attractive when you have many services sharing secrets, strict compliance requirements, or a large team that needs granular, auditable access. In those cases, the added complexity pays off by providing short-lived credentials, centralized policies and better audit trails. You can still run such a secret manager on your own VPS or dedicated server, integrating it gradually rather than replacing .env files overnight.