Home About Lessons Blog On The Mic Contact Projects
Lessons DEPLOY · C#

Secret Management on Shared Hosting — web.config Approach

9 min read · Emre Ulutabak
1
Why isn't appsettings.json enough?

You need to write your DB password, mail credentials, and JWT key somewhere. But where?

appsettings.json is the first thing that comes to mind. Easy, fast. But there is a big risk: this file goes to Git along with your source code. If you push to GitHub, the passwords are exposed to everyone. In team settings, everyone can see them too.

What about tools like Azure Key Vault, HashiCorp Vault, Secret Manager? These are great solutions — but there is no access on shared hosting. No SSH, no root, no service installation.

The solution is simple but effective: the environmentVariables block inside web.config.

💡
appsettings.json goes to Git. Don't put passwords there. Add web.config to .gitignore or keep it separate in production.
2
web.config environmentVariables

The web.config file lives on the server and does not go to Git. The environment variables you put inside it are automatically added to ASP.NET Core's configuration system and override the same keys in appsettings.json.

xml
<aspNetCore processPath="dotnet" arguments=".\MyApp.dll"
            stdoutLogEnabled="false"
            stdoutLogFile=".\logs\stdout"
            hostingModel="outofprocess">
  <environmentVariables>

    <!-- DB bağlantısı — şifre burada, appsettings.json'da değil -->
    <environmentVariable name="ConnectionStrings__DefaultConnection"
      value="Server=.\SQLEXPRESS;Database=MyDb;User Id=sa;Password=Sifre123;TrustServerCertificate=True;" />

    <!-- Mail sunucusu ayarları -->
    <environmentVariable name="EmailSettings__SmtpServer" value="smtp.brevo.com" />
    <environmentVariable name="EmailSettings__Port"       value="587" />
    <environmentVariable name="EmailSettings__Username"   value="kullanici@ornek.com" />
    <environmentVariable name="EmailSettings__Password"   value="MailSifresi" />

    <!-- JWT — private key dosyasının yolu -->
    <environmentVariable name="Jwt__PrivateKeyPath" value="private.key" />

  </environmentVariables>
</aspNetCore>
💡
TrustServerCertificate=True: SQL Server SSL certificate validation causes issues on shared hosting. Without this, you will get a connection error.
3
XML vs JSON escape difference

A common mistake when writing connection strings: a connection string that works in JSON doesn't work in XML.

The reason is simple: in JSON, backslash (\) is a special character — you need to write \\ to represent it. In XML, backslash is not a special character — you write it as \ directly.

json
// appsettings.json — JSON'da \\ gerekir
{
  "ConnectionStrings": {
    "DefaultConnection": "Server=.\\SQLEXPRESS;Database=MyDb;..."
  }
}
xml
<!-- web.config — XML'de tek \ yeterli -->
<environmentVariable name="ConnectionStrings__DefaultConnection"
  value="Server=.\SQLEXPRESS;Database=MyDb;..." />
💡
Single \ in XML, double \\ in JSON. If you mix these up, the connection fails but the error message might be misleading.
4
CopyToPublishDirectory setting

When dotnet publish runs, which files are included in the publish output is controlled from the .csproj file.

Two important rules:

  • appsettings.Production.json and appsettings.Development.jsonNever — these files may contain secrets, they should not go into the publish output.
  • Files like private.keyAlways — the application needs this file, it must be in the publish output.
xml
<!-- .csproj -->
<ItemGroup>
  <!-- Sırlar içerebilir — publish'e gitmesin -->
  <Content Update="appsettings.Production.json">
    <CopyToPublishDirectory>Never</CopyToPublishDirectory>
  </Content>
  <Content Update="appsettings.Development.json">
    <CopyToPublishDirectory>Never</CopyToPublishDirectory>
  </Content>

  <!-- JWT imzalamak için gerekli — publish'e gitsin -->
  <Content Include="private.key">
    <CopyToPublishDirectory>Always</CopyToPublishDirectory>
  </Content>
</ItemGroup>
5
stdoutLogEnabled — production log

What do you do when the site won't start in production or you get strange errors? No SSH, no Visual Studio debugger. The only solution: logs.

When the stdoutLogEnabled attribute is set to true, the application output is written to the logs/ folder. Find the error, fix it, then set it back to false.

xml
<!-- Normalde: production'da kapalı -->
<aspNetCore stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" ... />

<!-- Hata ayıklarken: aç, hatayı bul, kapat -->
<aspNetCore stdoutLogEnabled="true" stdoutLogFile=".\logs\stdout" ... />
💡
After setting stdoutLogEnabled=true, send a request to the site, then check the logs/ folder via FTP. The error message is there.
💡
After finding the error, set stdoutLogEnabled back to false. If left open, log files grow until they fill the disk.
6
Mini quiz
MINI QUIZ
What should the CopyToPublishDirectory value be for appsettings.Production.json?
Always — always include in publish
Never — do not include in publish output
PreserveNewest — include if changed
This setting cannot be made in .csproj