April 29, 2026 7 min read

Hiding Connection Strings on a Linux VM (UAT/Production)

How to keep your database connection string off appsettings.json and stored in a protected file on the server — readable only by the service account running your .NET app.

Overview

The goal is to keep the connection string off appsettings.json and stored in a protected file on the server that only the service account (www-data) can read. This prevents developers with SSH access from accidentally (or intentionally) seeing production credentials.

Step 1 — Clear appsettings.json

1 Leave the connection string empty in your project. The real value will come from the server.
json
"ConnectionStrings": {
    "AppDbConnection": ""
}

Step 2 — Create the Secrets File

2 Sysadmin only — create a protected directory and secrets file on the server.
bash
sudo mkdir -p /etc/your-api
sudo nano /etc/your-api/secrets.env

Add the connection string inside the file:

env
ConnectionStrings__AppDbConnection=Host=...;Port=5432;Database=...;Password=...;Username=postgres;
Note

The __ (double underscore) maps to ConnectionStrings:AppDbConnection in .NET configuration automatically. No code changes needed.

Step 3 — Lock Down File Permissions

3 Sysadmin only — restrict the secrets file so only www-data can read it.
bash
sudo chown www-data:www-data /etc/your-api/secrets.env
sudo chmod 600 /etc/your-api/secrets.env

Only www-data can read the file. Developer accounts will get Permission denied if they try to open it.

Step 4 — Update the systemd Service File

4 Add the EnvironmentFile line to your systemd service so it loads the secrets on startup.
bash
sudo nano /etc/systemd/system/your-api-uat.service
ini
[Unit]
Description=your-api uat Service
After=network.target

[Service]
WorkingDirectory=/var/www/your-api/uat/app
ExecStart=/usr/bin/dotnet /var/www/your-api/uat/app/BF.Tracker.Api.dll
Restart=always
RestartSec=10
SyslogIdentifier=your-api-uat
User=www-data
EnvironmentFile=/etc/your-api/secrets.env
Environment=ASPNETCORE_ENVIRONMENT=Production
Environment=DOTNET_PRINT_TELEMETRY_MESSAGE=false

[Install]
WantedBy=multi-user.target

Step 5 — Reload and Restart the Service

5 Apply the changes by reloading the systemd daemon and restarting the service.
bash
sudo systemctl daemon-reload
sudo systemctl restart your-api-uat
Important

daemon-reload is required every time the service file is edited. Without it, systemd will still use the old version of the file.

Step 6 — Verify

6 Confirm the service started successfully and is running.
bash
sudo systemctl status your-api-uat

How It Works

Here's a summary of who can access the secrets file and why:

Who Can read secrets.env?
www-data (service account) ✅ Yes
Developer accounts ❌ No — Permission denied
Root / Sysadmin ✅ Yes

The app reads the connection string normally via .NET configuration — no code changes needed:

csharp
var conn = builder.Configuration.GetConnectionString("AppDbConnection");

Cleanup (When No Longer Needed)

If you need to fully remove the service and secrets file from the server:

bash
# Stop and remove the service
sudo systemctl stop your-api-uat
sudo systemctl disable your-api-uat
sudo rm /etc/systemd/system/your-api-uat.service
sudo systemctl daemon-reload

# Delete the secrets file
sudo rm -rf /etc/your-api
J
John Ripdos

Writing tutorials based on real tasks I encounter at work. Everything here is battle-tested and built from experience.