Application Settings
When you want to shape how Portway behaves at its core (logging, security, rate limiting, and service wiring), appsettings.json is where you'll spend your time. This page walks through each section, explains what it controls, and shows how you can override values per deployment without touching the base file.
Configuration Files
| File | Purpose | Priority |
|---|---|---|
appsettings.json |
Base configuration | Lowest |
appsettings.Development.json |
Development overrides | Medium |
appsettings.Production.json |
Production overrides | Highest |
| Environment variables | Runtime overrides | Highest |
Core Configuration Structure
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"PathBase": "",
"WebUi": {
"AdminApiKey": "",
"PublicOrigins": [],
"SecureCookies": false
},
"OpenApi": { ... },
"RateLimiting": { ... },
"ForwardedHeaders": { ... },
"RequestTrafficLogging": { ... },
"SqlConnectionPooling": { ... }
}Logging Configuration
Portway uses Serilog for structured logging with configurable sinks and filtering.
Basic Structure
{
"Serilog": {
"MinimumLevel": {
"Default": "Information",
"Override": {
"Microsoft": "Warning",
"Microsoft.EntityFrameworkCore.Database.Command": "Warning",
"System": "Warning",
"Microsoft.AspNetCore": "Warning"
}
},
"WriteTo": [
{
"Name": "Console"
},
{
"Name": "File",
"Args": {
"path": "log/portwayapi-.log",
"rollingInterval": "Day",
"fileSizeLimitBytes": 10485760,
"rollOnFileSizeLimit": true,
"retainedFileCountLimit": 10,
"buffered": true,
"flushToDiskInterval": "00:00:30"
}
}
]
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}Log Levels
| Level | Description | Use Case |
|---|---|---|
Debug |
Debugging information | Development troubleshooting |
Information |
General flow of events | Normal operations |
Warning |
Abnormal or unexpected events | Potential issues |
Error |
Error events | Application errors |
Fatal |
Critical failures | System failures |
Changing Log Levels
To change the logging level, modify the Default value in appsettings.json:
{
"Serilog": {
"MinimumLevel": {
"Default": "Debug" // Change to Debug, Information, Warning, Error, or Fatal
}
}
}OpenAPI Configuration
Portway generates OpenAPI documentation from your configured endpoints and exposes it through the Scalar UI at /docs.
Full Configuration
{
"OpenApi": {
"Enabled": true,
"BaseProtocol": "https",
"Title": "Portway: API Gateway",
"Version": "v1",
"Description": "This is Portway. A lightweight API gateway designed to integrate your platforms with your Windows environment. It provides a simple, fast and efficient way to connect various data sources and services.",
"Contact": {
"Name": "Jay Doe (Demo Company)",
"Email": "support@democompany.local"
},
"Footer": {
"Text": "Powered by Scalar",
"Target": "_blank",
"Url": "#"
},
"SecurityDefinition": {
"Name": "Bearer",
"Description": "JWT Authorization header using the Bearer scheme. Example: \"Bearer {token}\"",
"In": "Header",
"Type": "ApiKey",
"Scheme": "Bearer"
},
"ScalarTheme": "default",
"ScalarLayout": "modern",
"ScalarShowSidebar": true,
"ScalarHideDownloadButton": true,
"ScalarHideModels": true,
"ScalarHideClientButton": true,
"ScalarHideTestRequestButton": false
}
}Property Reference
| Property | Type | Default | Description |
|---|---|---|---|
Enabled |
boolean | true |
Enable/disable API documentation |
BaseProtocol |
string | "https" |
Protocol for API base URLs |
Title |
string | - | Main title displayed in documentation |
Version |
string | "v1" |
API version identifier |
Description |
string | - | Main description (supports markdown) |
Contact |
object | - | Contact information for API support |
Footer |
object | - | Footer text shown in Scalar |
SecurityDefinition |
object | - | Authentication method configuration |
ScalarTheme |
string | "default" |
Scalar theme: "default", "alternate", "moon", "purple", "solarized" |
ScalarLayout |
string | "modern" |
Scalar layout: "modern", "classic" |
ScalarShowSidebar |
boolean | true |
Display navigation sidebar |
ScalarHideDownloadButton |
boolean | true |
Hide OpenAPI spec download button |
ScalarHideModels |
boolean | true |
Hide data models section |
ScalarHideClientButton |
boolean | true |
Hide client generation button |
ScalarHideTestRequestButton |
boolean | false |
Hide test request button |
Rate Limiting Configuration
Configuration Structure
{
"RateLimiting": {
"Enabled": true,
"IpLimit": 100,
"IpWindow": 60,
"TokenLimit": 100,
"TokenWindow": 60,
"Store": "Memory"
}
}Property Reference
| Property | Type | Default | Description |
|---|---|---|---|
Enabled |
boolean | true |
Enable rate limiting |
IpLimit |
integer | 100 |
Requests per IP |
IpWindow |
integer | 60 |
Time window in seconds |
TokenLimit |
integer | 100 |
Requests per token |
TokenWindow |
integer | 60 |
Time window in seconds |
Store |
string | Memory |
Bucket storage backend, Memory or Redis |
RedisConnectionString |
string | none | Redis connection for the Redis store, reuses the caching connection when empty |
Rate Limiting Behavior
- IP-based limiting applies to all requests
- Token-based limiting applies per authentication token, and individual tokens can carry their own limit that overrides
TokenLimit - Exceeding limits results in 429 Too Many Requests
- Buckets refill continuously over the time window
You can find a full walkthrough, including per-token limits and the Redis store, in the rate limiting guide.
Forwarded Headers
When Portway runs directly on Kestrel, the client IP it sees is the one connecting to it, which is exactly what per-IP rate limiting and the Web UI network gate rely on. Once you place a reverse proxy in front (nginx, IIS, or similar), that connecting IP becomes the proxy instead, and every client starts to look like the same address. The real client IP is still available in the X-Forwarded-For header, and this section is how you tell Portway which proxies it can trust to set it.
Configuration Structure
{
"ForwardedHeaders": {
"KnownProxies": ["127.0.0.1", "::1"],
"KnownNetworks": ["10.0.0.0/8"]
}
}Property Reference
| Property | Type | Default | Description |
|---|---|---|---|
KnownProxies |
string[] | [] |
IP addresses of trusted reverse proxies |
KnownNetworks |
string[] | [] |
CIDR ranges of trusted reverse proxies |
How It Works
Portway only honors X-Forwarded-For when the request arrives from an address you have listed here, which keeps clients from spoofing their own IP. You can register individual proxy addresses in KnownProxies, describe a whole range in KnownNetworks, or combine both when your setup calls for it. A common starting point for a proxy sharing the host with Portway is 127.0.0.1 and ::1.
Leaving both lists empty is perfectly valid, and it is the default. In that case X-Forwarded-For is ignored entirely and the connecting address is used as-is. That is the safe choice when nothing sits in front of Portway, though behind a proxy it means per-IP rate limiting and the network-based Web UI gate will see the proxy rather than the real caller. If you rely on either of those, registering your proxy here is recommended.
Note
If you front Portway with Cloudflare, its client IP is recovered separately from the CF-Connecting-IP header when the request genuinely originates from a Cloudflare address, so you do not need to list Cloudflare ranges here.
The Settings posture panel in the Web UI reflects whether any trusted proxies are configured, which is a quick way to confirm the setup took effect.
Request Traffic Logging
Full Configuration
{
"RequestTrafficLogging": {
"Enabled": false,
"QueueCapacity": 10000,
"StorageType": "file",
"SqlitePath": "log/traffic_logs.db",
"LogDirectory": "log/traffic",
"MaxFileSizeMB": 50,
"MaxFileCount": 5,
"FilePrefix": "proxy_traffic_",
"BatchSize": 100,
"FlushIntervalMs": 1000,
"IncludeRequestBodies": false,
"IncludeResponseBodies": false,
"MaxBodyCaptureSizeBytes": 4096,
"CaptureHeaders": true,
"EnableInfoLogging": true
}
}Property Reference
| Property | Type | Default | Description |
|---|---|---|---|
Enabled |
boolean | false |
Enable traffic logging |
QueueCapacity |
integer | 10000 |
Log queue size |
StorageType |
string | "file" |
Storage type: "file" or "sqlite" |
SqlitePath |
string | "log/traffic_logs.db" |
SQLite database path |
LogDirectory |
string | "log/traffic" |
Log file directory |
MaxFileSizeMB |
integer | 50 |
Maximum log file size |
MaxFileCount |
integer | 5 |
Maximum log files |
FilePrefix |
string | "proxy_traffic_" |
Log file prefix |
BatchSize |
integer | 100 |
Batch write size |
FlushIntervalMs |
integer | 1000 |
Flush interval (ms) |
IncludeRequestBodies |
boolean | false |
Log request bodies |
IncludeResponseBodies |
boolean | false |
Log response bodies |
MaxBodyCaptureSizeBytes |
integer | 4096 |
Max body size to log |
CaptureHeaders |
boolean | true |
Log request headers |
EnableInfoLogging |
boolean | true |
Enable info-level logs |
SQL Connection Pooling
Configuration Structure
{
"SqlConnectionPooling": {
"ApplicationName": "Portway API - Remote integration gateway",
"MinPoolSize": 5,
"MaxPoolSize": 100,
"ConnectionTimeout": 15,
"CommandTimeout": 30,
"Enabled": true
}
}Property Reference
| Property | Type | Default | Description |
|---|---|---|---|
ApplicationName |
string | - | Application identifier |
MinPoolSize |
integer | 5 |
Minimum pool connections |
MaxPoolSize |
integer | 100 |
Maximum pool connections |
ConnectionTimeout |
integer | 15 |
Connection timeout (seconds) |
CommandTimeout |
integer | 30 |
Command timeout (seconds) |
Enabled |
boolean | true |
Enable connection pooling |
Caching Configuration
Portway caches proxy and SQL responses in memory or Redis to reduce upstream load and improve response times.
Configuration Structure
{
"Caching": {
"Enabled": true,
"DefaultCacheDurationSeconds": 300,
"ProviderType": "Memory",
"MemoryCacheMaxItems": 10000,
"MemoryCacheSizeLimitMB": 100,
"CacheableContentTypes": [
"application/json",
"text/json"
],
"EndpointCacheDurations": {
"Products": 600,
"Customers": 300
},
"Redis": {
"ConnectionString": "localhost:6379",
"InstanceName": "Portway:",
"Database": 0,
"UseSsl": false,
"ConnectTimeoutMs": 5000,
"AbortOnConnectFail": false,
"FallbackToMemoryCache": true,
"MaxRetryAttempts": 3,
"RetryDelayMs": 200
}
}
}Property Reference
| Property | Type | Default | Description |
|---|---|---|---|
Enabled |
boolean | true |
Enable response caching |
DefaultCacheDurationSeconds |
integer | 300 |
Default TTL for cached responses |
ProviderType |
string | "Memory" |
Cache backend: "Memory" or "Redis" |
MemoryCacheMaxItems |
integer | 10000 |
Maximum number of items in the memory cache |
MemoryCacheSizeLimitMB |
integer | 100 |
Memory cap for the cache in MB |
CacheableContentTypes |
array | ["application/json", ...] |
Only responses with these content types are cached |
EndpointCacheDurations |
object | {} |
Per-endpoint TTL overrides keyed by endpoint name |
Redis Properties
| Property | Type | Default | Description |
|---|---|---|---|
ConnectionString |
string | "localhost:6379" |
Redis connection string |
InstanceName |
string | "Portway:" |
Key prefix to namespace cache entries |
Database |
integer | 0 |
Redis logical database index |
UseSsl |
boolean | false |
Use TLS for the Redis connection |
ConnectTimeoutMs |
integer | 5000 |
Connection timeout in milliseconds |
AbortOnConnectFail |
boolean | false |
Throw on connection failure instead of retrying |
FallbackToMemoryCache |
boolean | true |
Fall back to in-process memory cache if Redis is unavailable |
MaxRetryAttempts |
integer | 3 |
Retry attempts on transient Redis errors |
RetryDelayMs |
integer | 200 |
Delay between retry attempts in milliseconds |
File Storage Configuration
Controls how Portway stores and serves files for File-type endpoints.
Configuration Structure
{
"FileStorage": {
"StorageDirectory": "storage/files",
"MaxFileSizeBytes": 52428800,
"UseMemoryCache": true,
"MemoryCacheTimeSeconds": 60,
"MaxTotalMemoryCacheMB": 200,
"BlockedExtensions": [".exe", ".dll", ".bat", ".sh", "..."]
}
}Property Reference
| Property | Type | Default | Description |
|---|---|---|---|
StorageDirectory |
string | "storage/files" |
Directory where uploaded files are stored |
MaxFileSizeBytes |
integer | 52428800 |
Maximum upload size (default 50 MB) |
UseMemoryCache |
boolean | true |
Cache frequently accessed files in memory |
MemoryCacheTimeSeconds |
integer | 60 |
How long a file stays in the memory cache |
MaxTotalMemoryCacheMB |
integer | 200 |
Total memory budget for the file cache |
BlockedExtensions |
array | [".exe", ".dll", ...] |
File extensions that are refused on upload |
INFO
The default block list covers executable, script, and macro-enabled office formats. Extend it to suit your security policy; shrink it only with caution.
Endpoint Reloading Configuration
Controls hot-reload behaviour when endpoint JSON files change on disk.
Configuration Structure
{
"EndpointReloading": {
"Enabled": true,
"DebounceMs": 2000,
"LogLevel": "Information"
}
}Property Reference
| Property | Type | Default | Description |
|---|---|---|---|
Enabled |
boolean | true |
Watch endpoint files and reload on change without restart |
DebounceMs |
integer | 2000 |
Minimum milliseconds between reloads for the same file (prevents double-fires from editors) |
LogLevel |
string | "Information" |
Log level for reload events ("Debug", "Information", "Warning") |
Telemetry
Controls how request traces and metrics leave the gateway. Telemetry is off by default; selecting a provider activates it.
| Field | Required | Type | Description |
|---|---|---|---|
Provider |
No | string | "None", "Otlp", or "Prometheus". Defaults to "None" |
ServiceName |
No | string | Service name reported to the backend. Defaults to Portway.Api |
ResourceAttributes |
No | string | Comma-separated key=value pairs attached as resource attributes |
Otlp:Endpoint |
No | string | gRPC endpoint of your OTLP collector. Defaults to http://localhost:4317 |
Prometheus:Path |
No | string | Route the scrape endpoint is served on. Defaults to /metrics |
"Telemetry": {
"Provider": "Otlp",
"ServiceName": "portway-prod",
"ResourceAttributes": "deployment.environment=production,host.name=gw01",
"Otlp": {
"Endpoint": "http://otel-collector.internal:4317"
},
"Prometheus": {
"Path": "/metrics"
}
}The Otlp provider pushes traces and metrics to a collector over gRPC; the Prometheus provider serves metrics on a scrape endpoint instead. Configurations from earlier releases keep working: a flat "Enabled": true selects the OTLP provider, and a flat OtlpEndpoint is used whenever Otlp:Endpoint is not set. See the Telemetry guide for the full walkthrough.
MCP Configuration
Controls the Model Context Protocol server and built-in Chat feature.
Configuration Structure
{
"Mcp": {
"Enabled": true,
"Path": "/mcp",
"RequireAuthentication": true,
"AppsEnabled": true,
"ChatEnabled": false,
"DefaultPageSize": 50,
"MaxPageSize": 200,
"ToolTimeoutSeconds": 30,
"MaxToolResultChars": 12000
}
}Property Reference
| Property | Type | Default | Description |
|---|---|---|---|
Enabled |
boolean | false |
Activate the MCP HTTP server |
Path |
string | "/mcp" |
HTTP path the MCP server is mounted on |
RequireAuthentication |
boolean | true |
Require a valid Portway Bearer token on MCP requests |
AppsEnabled |
boolean | true |
Register embedded UI resources as MCP resource URIs |
ChatEnabled |
boolean | false |
Activate the Chat UI and SSE endpoint. Provider and credentials are configured via the setup wizard, not in this file |
DefaultPageSize |
integer | 50 |
Rows added as $top when the AI model omits a page size |
MaxPageSize |
integer | 200 |
Maximum $top value; the server clamps higher values to this limit |
ToolTimeoutSeconds |
integer | 30 |
HTTP timeout for internal tool-execution calls |
MaxToolResultChars |
integer | 12000 |
Maximum characters kept from a single tool result; longer results are truncated |
INFO
Chat credentials (provider, model, API key) are stored in the encrypted mcp.db database and managed through the Chat setup wizard. They are not configured in appsettings.json. An environment variable PORTWAY_CHAT_API_KEY can be used instead of the database entry; it takes precedence if set.
Log Settings
Configuration Structure
{
"LogSettings": {
"LogResponseToFile": false
}
}| Property | Type | Default | Description |
|---|---|---|---|
LogResponseToFile |
boolean | false |
Write raw response bodies to the log file (useful for debugging; disable in production) |
General Settings
AllowedHosts
{
"AllowedHosts": "*"
}Configure which hosts can access the application:
"*"- Allow all hosts"example.com"- Allow specific domain"*.example.com"- Allow subdomains"example.com;api.example.com"- Multiple hosts
PathBase
{
"PathBase": ""
}Base path for the application (e.g., /api).
Web UI Configuration
The built-in admin interface settings.
{
"WebUi": {
"AdminApiKey": "your-secure-password",
"PublicOrigins": ["https://example.com"],
"SecureCookies": true,
"EnableLandingPage": true
}
}Property Reference
| Property | Type | Default | Description |
|---|---|---|---|
AdminApiKey |
string | "" |
Password for web UI login (empty = disabled) |
PublicOrigins |
array | [] |
Allowed CORS origins for external access |
SecureCookies |
boolean | false |
Require HTTPS for auth cookies |
EnableLandingPage |
boolean | true |
Show the landing page at / for local/allowed clients. Set to false to redirect all root requests straight to /docs (useful for production systems where the UI should not be discoverable). |
Customization.PromoText |
string | "" |
Markdown banner shown at the top of the login page |
Customization.PromoLogin |
boolean | false |
Allow the promo-bar to be shown at /login |
Customization.LoginFooter |
string | "" |
Markdown text shown below the login form |
Security
- Without
AdminApiKey, the web UI is disabled - Without
PublicOrigins, only local network IPs can access the UI - Cookie auth uses HMAC-SHA256 signing
- Set
EnableLandingPage: falseon internet-facing or production deployments to prevent the admin UI from being surfaced at the root path
Customization Example
{
"WebUi": {
"Customization": {
"PromoText": "Welcome to **Portway**. Check the [documentation](https://github.com/melosso/portway) to get started.",
"LoginFooter": "No account? Contact your [administrator](mailto:admin@example.com)."
}
}
}Both fields support standard Markdown (bold, links, inline code).
Environment-Specific Configuration
Development Settings
appsettings.Development.json:
{
"Logging": {
"LogLevel": {
"Default": "Debug",
"Microsoft.EntityFrameworkCore.Database.Command": "Information"
}
},
"RequestTrafficLogging": {
"Enabled": true,
"IncludeRequestBodies": true,
"IncludeResponseBodies": true
}
}Production Settings
appsettings.Production.json:
{
"Logging": {
"LogLevel": {
"Default": "Warning",
"PortwayApi": "Information"
}
},
"RateLimiting": {
"IpLimit": 200,
"TokenLimit": 1000
},
"AllowedHosts": "api.company.com"
}Security Settings
CORS Configuration
CORS is configured to allow all origins in the default configuration:
{
"AllowedHosts": "*"
}For production, restrict to specific domains:
{
"AllowedHosts": "api.company.com;app.company.com"
}Performance Tuning
Connection Pool Optimization
{
"SqlConnectionPooling": {
"MinPoolSize": 10,
"MaxPoolSize": 200,
"ConnectionTimeout": 30,
"CommandTimeout": 60,
"Enabled": true
}
}Rate Limiting for High Traffic
{
"RateLimiting": {
"Enabled": true,
"IpLimit": 500,
"IpWindow": 60,
"TokenLimit": 5000,
"TokenWindow": 60
}
}Traffic Logging for Debugging
{
"RequestTrafficLogging": {
"Enabled": true,
"StorageType": "sqlite",
"IncludeRequestBodies": true,
"IncludeResponseBodies": true,
"MaxBodyCaptureSizeBytes": 8192
}
}Environment Variables
Common Variables
| Variable | Description | Example |
|---|---|---|
PORTWAY_ENCRYPTION_KEY |
Encryption secret | (Hardcoded) |
PORTWAY_CHAT_API_KEY |
AI provider API key for Chat. Takes precedence over the encrypted database entry when set. | sk-ant-... |
Use_HTTPS |
Whether Kestrel serves HTTPS directly (see note) | false |
KEYVAULT_URI |
Azure Key Vault URI | https://vault.azure.net |
PROXY_USERNAME |
Proxy authentication user | domain\user |
PROXY_PASSWORD |
Proxy authentication password | password |
PROXY_DOMAIN |
Proxy domain | CONTOSO |
AllowedHosts |
Allowed host names | * |
PathBase |
Base path | /api |
Mcp__ChatEnabled |
Override Mcp:ChatEnabled at runtime |
true |
WebUi__AdminApiKey |
Web UI password | secret |
WebUi__PublicOrigins__0 |
CORS origin (array) | https://example.com |
WebUi__SecureCookies |
Secure cookies | true |
WebUi__EnableLandingPage |
Show landing page at root | false |
WARNING
Use_HTTPS=true requires a TLS certificate reachable by Kestrel. Without one the container fails immediately at startup with BackgroundService failed / Hosting failed to start. In Docker deployments where an external reverse proxy (nginx, Caddy, Cloudflare Tunnel, etc.) handles SSL termination, leave this unset or set it to false. Only enable it when Portway is directly internet-facing and a certificate is supplied (e.g. via Kestrel__Certificates__Default__Path).
Configuration Priority
- Environment variables
appsettings.{Environment}.jsonappsettings.json- Default values
Troubleshooting Configuration
Common Issues
Application Won't Start
- Check JSON syntax in appsettings files
- Verify required environment variables
- Review startup logs
Database Connection Failures
- Verify connection strings
- Check SQL Server availability
- Review firewall settings
Rate Limiting Too Restrictive
- Adjust IpLimit and TokenLimit
- Increase time windows
- Monitor traffic patterns
Logging Not Working
- Check log file permissions
- Verify log directory exists
- Review LogLevel settings
Configuration Debugging
- Enable detailed logging:
{
"Logging": {
"LogLevel": {
"Default": "Debug",
"Microsoft": "Information"
}
}
}- Check environment variable:
- Review startup logs for configuration issues
Complete Example Configuration
Production appsettings.json
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"Microsoft.EntityFrameworkCore": "Warning"
}
},
"AllowedHosts": "api.company.com",
"OpenApi": {
"Enabled": true,
"BaseProtocol": "https",
"Title": "Company API Gateway",
"Version": "v1",
"Description": "Production API Gateway",
"Contact": {
"Name": "API Support",
"Email": "api-support@company.com"
},
"SecurityDefinition": {
"Name": "Bearer",
"Description": "Enter 'Bearer' [space] and then your token",
"In": "Header",
"Type": "ApiKey",
"Scheme": "Bearer"
}
},
"RateLimiting": {
"Enabled": true,
"IpLimit": 200,
"IpWindow": 60,
"TokenLimit": 2000,
"TokenWindow": 60
},
"RequestTrafficLogging": {
"Enabled": false,
"StorageType": "sqlite",
"SqlitePath": "log/traffic.db",
"CaptureHeaders": true,
"IncludeRequestBodies": false,
"IncludeResponseBodies": false
},
"SqlConnectionPooling": {
"ApplicationName": "Company API Gateway",
"MinPoolSize": 10,
"MaxPoolSize": 150,
"ConnectionTimeout": 30,
"CommandTimeout": 30,
"Enabled": true
}
}Related Topics
- Environment Settings - Environment-specific configuration
- Security Guide - Security configuration
- Deployment Guide - Production deployment
- Logging - Logging configuration
Portway