SecurePACS Vault Gateway User Manual
The SecurePACS Vault Gateway is a high-performance DICOM-to-Web gateway. It listens for incoming DICOM studies via DIMSE, stores them locally, and securely uploads them to SecurePACS with end-to-end encryption.
Secure by Design
All studies are encrypted locally before being transmitted to SecurePACS. Sensitive DICOM tags are protected using public-key cryptography (P-521 ECDH + AES-CBC-256), ensuring that only authorized recipients can view the data.
Quick Start
Get the gateway running in 4 easy steps.
1. Install
Download the binaries or build from source using .NET 8.
2. Setup Wizard
Run the app. The interactive wizard guides you through mandatory settings and saves them to appsettings.Local.json.
3. Run
Start as a Windows Service or systemd daemon on Linux.
4. Dashboard
Open http://localhost:11113 to monitor studies, logs, and configuration in real-time.
Interactive Setup Wizard
If Gateway:SecurePacs:PublicKeyId is not configured, the gateway enters Setup Mode and serves a browser-based setup wizard at http://127.0.0.1:11113/setup.html. The wizard guides you through mandatory settings and saves them to appsettings.Local.json, then automatically restarts into normal mode. When deploying as a headless service, pre-populate appsettings.Local.json or set Gateway__SecurePacs__PublicKeyId via environment variable before first start. See the Setup Mode section for details.
Installation
Deploying the gateway as a background service.
Build
dotnet publish -c Release -r win-x64 --self-contained false -o ./publish
Adjust -r for your platform: win-x64, win-arm64, linux-x64, linux-arm64.
Windows Service
The gateway can be installed using PowerShell (Run as Administrator):
New-Service -Name "SecurePACSVaultGateway" `
-BinaryPathName "C:\Path\To\Gateway\SecurePACS.Vault.Gateway.exe" `
-DisplayName "SecurePACS Vault Gateway" `
-Description "Receives DICOM studies and uploads them to SecurePACS." `
-StartupType Automatic
Start-Service "SecurePACSVaultGateway"
Linux (systemd)
Create /etc/systemd/system/SecurePACS-gateway.service:
[Unit]
Description=SecurePACS Vault Gateway
After=network.target
[Service]
Type=notify
ExecStart=/path/to/gateway/SecurePACS.Vault.Gateway
WorkingDirectory=/path/to/gateway
User=gatewayuser
Group=gatewaygroup
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
Then run: sudo systemctl enable --now SecurePACS-gateway
Configuration Layering
Settings are resolved in this order (later overrides earlier):
1. appsettings.json — ships with defaults; do not edit directly.
2. appsettings.Local.json — written by the wizard and dashboard; place your overrides here.
3. Environment variables — use Gateway__Key__SubKey=value (double underscores).
Setup Mode
First-run web wizard for mandatory configuration.
When the gateway starts without a configured Gateway:SecurePacs:PublicKeyId it enters Setup Mode. In Setup Mode the gateway starts a minimal web server and redirects all requests to /setup.html — a guided browser wizard that collects the required settings.
What the Wizard Collects
- Gateway Instance ID
- SecurePACS Public Key ID and Base URL
- DICOM AE Title and listener port
- Storage and database paths
- Optional admin password (hashed before saving)
How It Works
- The wizard calls
GET /api/setup/statusto confirm it is in Setup Mode. - A folder-browser widget calls
POST /api/setup/folders/list(loopback only) to help select local paths. - On completion, the wizard calls
POST /api/setup/savewhich writesappsettings.Local.jsonand restarts the gateway into normal operational mode after a 2-second delay.
Setup API is Loopback-Only
All /api/setup/* endpoints are restricted to 127.0.0.1 / ::1. They are not accessible from remote machines. To re-run the wizard after initial configuration, use POST /system/config/reset from loopback (or delete appsettings.Local.json manually) and restart.
Configuration
All settings live under the Gateway: key in appsettings.json / appsettings.Local.json.
| Key | Default | Description |
|---|---|---|
Gateway:InstanceId | securepacs-gateway-01 | Unique name for this gateway instance. Shown in the startup banner and status responses. |
| Key | Default | Description |
|---|---|---|
Gateway:Dicom:AeTitle | SECUREPACS_GW | Application Entity Title advertised to modalities. |
Gateway:Dicom:MaxAssociations | 16 | Maximum concurrent DICOM associations (0 = unlimited). |
Gateway:Dicom:AssociationIdleTimeoutSeconds | 120 | Seconds before an idle association is closed. |
Gateway:Dicom:AcceptUnknownCallingAeTitles | true | When true, accepts connections from any calling AE. Set to false and populate AllowedCallingAeTitles to restrict access. |
Gateway:Dicom:AllowedCallingAeTitles | [] | Whitelist of AE Titles allowed to connect when AcceptUnknownCallingAeTitles is false. |
Gateway:Dicom:MaxConnectionHistory | 30 | Number of past DICOM associations persisted in the database for the Connections dashboard page. |
All listeners (DICOM and HTTP/HTTPS) are configured through Gateway:Network:Endpoints, a named dictionary. Each entry has:
| Field | Description |
|---|---|
Url | Required. Absolute URI. Use dicom://, dicomtls://, http://, or https://. |
Certificate | Optional. Name of an entry in Gateway:Network:Certificates to use for TLS. |
MinTlsVersion | Tls12 (TLS 1.2+, default) or Tls13 (TLS 1.3 only). |
Certificates are defined in Gateway:Network:Certificates:
| Field | Description |
|---|---|
Source | File (only supported source currently). |
Path | Absolute or relative path to the PFX/PKCS#12 file. |
Password | PFX password. Prefer the GATEWAY_CERT_PASSWORD environment variable over storing here. |
| Key | Default | Description |
|---|---|---|
Gateway:SecurePacs:BaseUrl | https://secure.dicom.link | URL of the SecurePACS server. |
Gateway:SecurePacs:PublicKeyId | Required | Organization or user public key ID used to encrypt uploads. |
Gateway:SecurePacs:BearerToken | null | Optional bearer token sent with every request to the SecurePACS server. |
Gateway:SecurePacs:HttpTimeout | 00:05:00 | Timeout for individual HTTP calls to the SecurePACS server. |
Gateway:SecurePacs:StudyMessageTemplate | see below | Template for the study message attached to each upload. Placeholders: {AppName}, {AppVersion}, {GatewayAeTitle}, {GatewayPort}, {SourceAeTitle}. Max 4096 characters. |
Study uploaded from {AppName} {AppVersion}.\nReceived by {GatewayAeTitle} on DIMSE port {GatewayPort}.\nSource AE: {SourceAeTitle}.| Key | Default | Description |
|---|---|---|
Gateway:Storage:Provider | Local | Storage backend. Currently only Local (filesystem) is supported. |
Gateway:Storage:LocalRoot | data/studies | Path where raw DICOM files are temporarily stored. |
Gateway:Storage:DeleteRawFilesAfterUpload | false | Global safety gate: both this and Cleanup:Enabled must be true for automatic file deletion. Keep false until upload behavior is manually validated. |
Gateway:Storage:RetainUploadedStudiesFor | 7.00:00:00 (7 days) | How long an uploaded study's files are kept before the Retention cleanup policy considers them eligible for deletion. |
Gateway:Storage:LowSpaceThresholdGb | 5 | Free-space threshold in GB that triggers LowSpace cleanup policies. |
| Key | Default | Description |
|---|---|---|
Gateway:Database:ConnectionString | Data Source=data/gateway.sqlite | SQLite connection string. WAL mode is enabled automatically. |
Gateway:DatabaseAdmin:MaintenanceInterval | 1.00:00:00 (24 h) | How often the scheduled maintenance task runs. |
Gateway:DatabaseAdmin:EnableScheduledVacuum | false | When true, the maintenance task automatically vacuums the database on each run. |
Gateway:DatabaseAdmin:MaxRestoreSizeMb | 1024 | Maximum size in MB accepted for database restore uploads via the API (1–10240). |
| Key | Default | Description |
|---|---|---|
Gateway:StudySettling:InactivityTimeout | 00:10:00 (10 min) | How long to wait with no new instances before a study is considered settled and queued for upload. |
Gateway:StudySettling:ScanInterval | 00:00:30 (30 sec) | How frequently the settling service scans for candidates. |
| Key | Default | Description |
|---|---|---|
Gateway:UploadWorker:Enabled | true | Enable or disable the SecurePACS upload worker entirely. |
Gateway:UploadWorker:MaxAttempts | 0 | Maximum upload attempts per study. 0 = unlimited retries. |
Gateway:UploadWorker:IdleDelay | 00:00:10 (10 sec) | How long the upload worker waits when no work is available. |
Gateway:UploadWorker:ParallelFileUploads | 1 | Number of DICOM files uploaded in parallel within a single study (1–4). The worker always processes one study at a time; this only affects per-study file concurrency. |
| Key | Default | Description |
|---|---|---|
Gateway:Cleanup:Enabled | false | Activate the cleanup background worker. Also requires Storage:DeleteRawFilesAfterUpload=true. |
Gateway:Cleanup:Policy | Retention | Cleanup mode. See table below. |
Gateway:Cleanup:ScanInterval | 01:00:00 (1 hour) | How often the cleanup worker scans for eligible studies. |
Gateway:Cleanup:BatchSize | 25 | Maximum studies processed per cleanup scan. |
Gateway:Cleanup:MinimumAgeBeforeLowSpaceCleanup | 01:00:00 (1 hour) | Minimum age a study must have before low-space cleanup considers it, even under critical disk pressure. |
Cleanup Policy Values
| Value | Behavior |
|---|---|
Never | Cleanup worker does nothing even if enabled. |
Retention | Deletes files for uploaded studies older than Storage:RetainUploadedStudiesFor. |
ManualReview | Deletes only studies explicitly approved via POST /studies/{uid}/approve-cleanup. |
LowSpace | Deletes oldest uploaded studies when free space falls below LowSpaceThresholdGb, subject to MinimumAgeBeforeLowSpaceCleanup. |
RetentionOrLowSpace | Combines Retention and LowSpace — whichever triggers first. |
ManualReviewOrLowSpace | Combines ManualReview with an automatic low-space safety net. |
| Key | Default | Description |
|---|---|---|
Gateway:Logging:ConsoleLevel | Information | Minimum log level for console output. |
Gateway:Logging:FileLevel | Information | Minimum log level for rolling file output. |
Gateway:Logging:LogDirectory | data/logs | Directory where rolling log files are written. |
Gateway:Logging:ArchiveDirectory | data/logs/archive | Directory where archived/rotated log files are moved. Used by POST /system/logs/clear-archive. |
Gateway:Logging:MaxArchiveFiles | 14 | Number of archived log files to retain. |
Gateway:Logging:DashboardBufferLimit | 500 | Maximum number of recent log entries buffered in memory for the live-log dashboard feature (10–2000). |
| Key | Default | Description |
|---|---|---|
Gateway:StatusApi:Enabled | true | Enable or disable the web dashboard and status API entirely. |
Gateway:StatusApi:AllowNonLoopbackPrefixes | false | Allow binding to non-loopback addresses. When enabling external access, it is strongly recommended to also set a password and enable HTTPS. |
Gateway:StatusApi:AdminPasswordHash | null | Bcrypt-hashed dashboard password. When not set the dashboard runs in open mode (no authentication required). Set via the dashboard Settings → Security page or the POST /auth/change-password endpoint — never edit the hash manually. A startup warning is printed when the gateway is in open mode. |
Gateway:StatusApi:SessionVersion | 1 | Internal counter incremented automatically on every password change. All existing sessions issued at a lower version are immediately invalidated, providing instant revocation without server-side session storage. |
| Key | Default | Description |
|---|---|---|
Gateway:Hosting:EnableSelfRestart | false | When true, the gateway attempts to spawn a new process before shutting down during a restart request, enabling self-restart without an external process manager. Set to false (default) when running as a Windows Service, systemd unit, or inside Docker — the process manager handles restart automatically. |
Hot Reload
Most settings can be updated at runtime via the PATCH /config API or the Settings page in the dashboard. Changes to Network, Dicom, Storage, Database, DatabaseAdmin, Hosting, and StatusApi require a service restart to take effect. The API response indicates which sections need a restart. Password changes are applied immediately via POST /auth/change-password and take effect without a restart.
HTTPS Setup
Enabling TLS for the Status API and dashboard.
By default the dashboard is served over plain HTTP on loopback only (http://127.0.0.1:11113). When you enable HTTPS, the gateway adds a separate HTTPS endpoint — it does not replace the existing HTTP one. Both listeners run simultaneously: HTTP stays on its original port (e.g. 11113, loopback only), and HTTPS is added on port + 1 (e.g. 11114, all interfaces) by default. This way loopback health checks remain available over plain HTTP while external access uses HTTPS.
You need a PFX/PKCS#12 certificate to enable HTTPS. Self-signed certificates are fine for testing; see the section below for generation commands.
Option A — Via the Dashboard (Recommended)
- Open
http://127.0.0.1:11113and navigate to Settings → HTTPS Configuration. - Upload your PFX certificate file with its password and click Upload & Save. The certificate is written to
data/certs/and saved as theGatewayDefaultcertificate entry. - In the Network settings, add a new HTTPS endpoint referencing
GatewayDefaultand click Save Changes. - Restart the gateway (via the System → Restart button or the service manager) to activate HTTPS.
Option B — Via the API
Upload the certificate, then add the HTTPS endpoint via PATCH /config, then restart:
# Step 1: Upload the certificate (saves as GatewayDefault, does not add endpoint automatically).
curl -X POST http://127.0.0.1:11113/certs/upload \
-H "X-Gateway-Admin: true" \
-F "file=@/path/to/cert.pfx" \
-F "password=secret"
# Step 2: Add the HTTPS endpoint via config PATCH.
curl -X PATCH http://127.0.0.1:11113/config \
-H "X-Gateway-Admin: true" \
-H "Content-Type: application/json" \
-d '{"Network":{"Endpoints":{"StatusApiHttps":{"Url":"https://0.0.0.0:11114","Certificate":"GatewayDefault"}}}}'
# Step 3: Restart to activate.
curl -X POST http://127.0.0.1:11113/system/restart -H "X-Gateway-Admin: true"
Option C — Manual Edit
Add a StatusApiHttps entry alongside the existing StatusApi entry in appsettings.Local.json, then restart. The HTTP endpoint is untouched:
{
"Gateway": {
"Network": {
"Endpoints": {
"StatusApi": {
"Url": "http://127.0.0.1:11113/"
},
"StatusApiHttps": {
"Url": "https://0.0.0.0:11114",
"Certificate": "GatewayDefault",
"MinTlsVersion": "Tls12"
}
},
"Certificates": {
"GatewayDefault": {
"Source": "File",
"Path": "data/certs/THUMBPRINT.pfx",
"Password": "secret"
}
}
},
"StatusApi": {
"AllowNonLoopbackPrefixes": true,
"BearerToken": "your-secret-token"
}
}
}
DICOM TLS
The same dual-port pattern applies to the DICOM listener. To run both plain DICOM and DICOM-TLS simultaneously, add a second endpoint alongside the existing one:
{
"Gateway": {
"Network": {
"Endpoints": {
"DicomListener": {
"Url": "dicom://0.0.0.0:11112"
},
"DicomListenerTls": {
"Url": "dicomtls://0.0.0.0:11113",
"Certificate": "GatewayDefault"
}
}
}
}
}
Convention: TLS port = plain port + 1. Modalities that support DICOM-TLS can connect on the TLS port; legacy modalities continue on the plain port. Both listeners run at the same time with no interaction.
Keep the Password out of Config Files
Set the GATEWAY_CERT_PASSWORD environment variable instead of writing the PFX password to appsettings.Local.json. You can also use the standard .NET env-var override pattern: Gateway__Network__Certificates__GatewayDefault__Password=secret.
Generating a Self-Signed Certificate (Testing Only)
Windows (PowerShell):
$cert = New-SelfSignedCertificate -DnsName "localhost" -CertStoreLocation "cert:\LocalMachine\My"
$pwd = ConvertTo-SecureString "YourPassword" -AsPlainText -Force
Export-PfxCertificate -Cert $cert -FilePath "data\certs\gateway.pfx" -Password $pwd
Linux / macOS (openssl):
openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes -subj "/CN=localhost"
openssl pkcs12 -export -out data/certs/gateway.pfx -inkey key.pem -in cert.pem -passout pass:YourPassword
Validating Without Installing
Use POST /certs/validate to check a PFX file's validity, expiry, key size, and self-signed status before committing it. The dashboard Settings page includes a Validate button for this purpose.
Environment Variables
Override any setting without touching config files.
All configuration keys can be set via environment variables using double-underscores as the hierarchy separator:
Gateway__SecurePacs__PublicKeyId=your-key-id
Gateway__SecurePacs__BaseUrl=https://secure.dicom.link
Gateway__SecurePacs__BearerToken=optional-securepacs-token
Gateway__Storage__DeleteRawFilesAfterUpload=true
Gateway__Cleanup__Enabled=true
Gateway__Network__Certificates__GatewayDefault__Password=cert-password
Gateway__UploadWorker__ParallelFileUploads=4
Gateway__Hosting__EnableSelfRestart=true
Dashboard Password via Environment Variable
The dashboard admin password is stored as a bcrypt hash (AdminPasswordHash), not as a plaintext value. It should be set interactively through the Setup Wizard or the Settings → Security page, not via environment variable. Do not set Gateway__StatusApi__AdminPasswordHash directly unless you are providing a pre-computed bcrypt hash.
For Linux systemd units, add them to the [Service] section:
[Service]
Environment="Gateway__SecurePacs__PublicKeyId=your-key-id"
Environment="Gateway__StatusApi__BearerToken=your-secret-token"
For Windows services, set them via the service properties or in the system environment variables. The gateway picks them up on next start.
Internal Logic
How the gateway ensures study integrity and delivery.
The Study Lifecycle
DICOM lacks an explicit "End of Study" signal. The gateway uses a Settling Mechanism to determine when a study is ready for upload.
Study Status States
| Status | Meaning |
|---|---|
Receiving | Instances are actively being received from a modality. |
Settled | No new instances have arrived within the inactivity timeout; queued for upload. |
Uploading | The upload worker is currently processing this study. |
Uploaded | Successfully uploaded to SecurePACS. |
FailedTransient | Upload failed; will be retried according to the retry schedule. |
FailedPermanent | Upload failed and MaxAttempts was reached. No further automatic retries. Can be manually reset. |
PendingDeletion | Marked for cleanup; local file deletion is in progress. |
Deleted | Local files removed. Study metadata record is retained in the database. |
1. Study Settling
The StudySettlingService scans the local database for studies in the Receiving state every StudySettling:ScanInterval (default 30 seconds). If no new instances have been received for StudySettling:InactivityTimeout (default 10 minutes), the study is marked Settled and moved to the upload queue.
2. Secure Upload Worker
The upload worker processes studies one at a time to ensure system stability. It performs the following steps:
- Metadata Extraction: Scans local files to build a complete SecurePACS study map and study info objects.
- Local Encryption: Fetches the public key from SecurePACS, generates an ephemeral ECDH P-521 keypair, derives a shared secret, and creates an AES-256-CBC data key. All DICOM files are zstd-compressed (level 3) and AES-CBC encrypted before upload.
- Multipart Upload: Transmits encrypted DICOM files to SecurePACS. Tune
UploadWorker:ParallelFileUploads(1–4) to speed up large study transfers. - Finalization: Sends encrypted study map, study info, and metadata to the SecurePACS finalize endpoint.
3. Retry Policy
Failed uploads are retried automatically. Each attempt uses an exponential back-off:
| Attempt | Delay Before Retry |
|---|---|
| 1 | 1 minute |
| 2 | 5 minutes |
| 3 | 15 minutes |
| 4 | 1 hour |
| 5 | 3 hours |
| 6 | 6 hours |
| 7+ | 24 hours (with jitter) |
Set UploadWorker:MaxAttempts = 0 for unlimited retries. Any positive value marks the study FailedPermanent after that attempt count. Failed studies can be manually reset to Settled via the dashboard or POST /studies/{uid}/retry.
4. Cleanup Policies
Once a study is successfully uploaded, the StudyCleanupService manages the local files based on your policy (see the Cleanup configuration table above). Both Cleanup:Enabled = true and Storage:DeleteRawFilesAfterUpload = true must be set for any automatic deletion to occur.
5. Database Maintenance
The gateway automatically maintains its SQLite database. Every DatabaseAdmin:MaintenanceInterval (default 24 hours), the scheduled maintenance task runs:
- Integrity Check: Verifies the database file is not corrupted.
- Vacuum (if
EnableScheduledVacuum = true): Reclaims unused space and defragments the database.
On-demand vacuum, index rebuild, integrity check, backup, and restore are also available via the Database page in the dashboard or the /db/* API endpoints.
6. Upload Audit Trail
Every upload attempt is persisted in the database with start time, end time, result, error message, and the SecurePACS study ID assigned for that attempt. View the full audit trail for any study via GET /studies/{uid}/attempts or the study detail modal in the dashboard.
Web Dashboard
Built-in browser UI for monitoring and management.
Opening the gateway URL (default http://127.0.0.1:11113) in any modern browser loads the management dashboard. It has five pages accessible from the left sidebar:
MaxConnectionHistory completed associations stored in the database).
Logging:DashboardBufferLimit.
Backup & Restore: Download a live SQLite backup or upload a backup file to stage a restore (applied on next restart).
appsettings.Local.json; a restart is required to activate HTTPS.
Security: Set or change the dashboard admin password. When no password is set, the gateway runs in open mode and a warning banner is shown.
System Configuration: A full editor for all gateway options. Sections requiring restart are flagged. Click Save Changes to apply.
Dashboard Authentication
When StatusApi:AdminPasswordHash is configured, the dashboard shows a login form on first visit. Sessions last 8 hours with sliding expiration and are stored as an HttpOnly cookie. Changing the password immediately invalidates all existing sessions. When no password is configured, the gateway runs in open mode and prints a security warning at startup — suitable for loopback-only installs during initial setup.
Status API Reference
JSON API for automation and external monitoring.
Authentication
The dashboard uses cookie-based authentication. Two modes of operation exist:
- Open mode (no password configured): all endpoints are accessible without authentication. Suitable for loopback-only installs during initial setup. A warning is printed at startup and displayed in the dashboard.
- Password mode (
AdminPasswordHashset): a login form is shown on first visit. After successful login aGatewaySessionHttpOnly cookie is issued. Sessions last 8 hours with sliding expiration. Changing the password invalidates all existing sessions immediately.
- Exception — anonymous loopback:
GET /healthis always accessible from127.0.0.1/::1without authentication (useful for health-probe scripts). - Auth endpoints (
/auth/login,/auth/logout,/auth/status) are reachable without a valid session. - State-changing requests (POST, PATCH, DELETE) must include the
X-Gateway-Admin: trueheader as an additional safety guard against accidental writes. - Rate limiting:
POST /auth/loginandPOST /auth/change-passwordare limited to 5 requests per 15 minutes per IP. Exceeded attempts receive HTTP 429 with aRetry-After: 900header.
Authentication Endpoints
| Method | Endpoint | Description |
|---|---|---|
| POST | /auth/login | Authenticate with {"password":"..."}. Sets GatewaySession cookie on success. Rate-limited to 5 per 15 min per IP. |
| POST | /auth/logout | Clear the session cookie. |
| GET | /auth/status | Returns { isAuthenticated, user, openMode, insecureCookies }. Always accessible without a session. |
| POST | /auth/change-password | Change the admin password. Body: {"currentPassword":"...","newPassword":"..."}. New password must be ≥ 8 characters and contain at least one non-letter character. Invalidates all existing sessions. Rate-limited. |
System & Status
| Method | Endpoint | Description |
|---|---|---|
| GET | /health | Liveness check. Returns { status, instanceId, startupTime, utcNow }. Accessible from loopback without authentication even in password mode. |
| GET | /status | Detailed status: DICOM config, SecurePACS readiness, storage space, worker flags, cleanup evaluation, and queue counts by status (including totalSizeBytes). |
| GET | /system/diagnostics | System information: OS, architecture, .NET framework, PID, machine name, processor count, working set (MB), gateway version, instance ID, startup time, and uptime. |
| POST | /system/restart | Gracefully shuts down the process. When Hosting:EnableSelfRestart = true the gateway spawns a new process before exiting; otherwise the external service manager (systemd, Windows Service) handles the restart. |
| POST | /system/config/reset | Deletes appsettings.Local.json and restarts, returning the gateway to Setup Mode. Useful for recovering from a misconfiguration. Loopback-accessible only. |
| GET | /system/logs/export | Download all files in the configured log directory as a ZIP archive (gateway-logs-YYYYMMDD-HHmmss.zip). |
| POST | /system/logs/clear-archive | Delete all files in Logging:ArchiveDirectory, reclaiming disk space consumed by rotated log archives. |
Studies
| Method | Endpoint | Description |
|---|---|---|
| GET | /studies | List of up to 50 most recent studies and their current status. |
| GET | /studies/{uid} | Full detail for one study: overview, instance list, and upload attempt history. Optional query params: maxInstances (1–500, default 50), maxAttempts (1–200, default 50). |
| GET | /studies/{uid}/attempts | Full upload audit trail for one study. Optional: limit (1–200, default 100). |
| POST | /studies/{uid}/retry | Reset a failed study to Settled and wake the upload worker. Only works for FailedTransient and FailedPermanent statuses. |
| POST | /studies/{uid}/approve-cleanup | Mark an uploaded study as approved for manual-review cleanup policies. |
| DELETE | /studies/{uid} | Manually delete a study's local files and mark it Deleted. Only allowed for studies in Uploaded status. |
Connections
| Method | Endpoint | Description |
|---|---|---|
| GET | /connections | Active DICOM associations and persisted connection history (last MaxConnectionHistory records). |
Configuration
| Method | Endpoint | Description |
|---|---|---|
| GET | /config | Read the full current configuration. Sensitive fields (bearer tokens, certificate passwords) are redacted. |
| PATCH | /config | Apply a partial configuration update. Body is a JSON object merged into the Gateway section of appsettings.Local.json. Response indicates which sections require a restart. |
Logs
| Method | Endpoint | Description |
|---|---|---|
| GET | /logs | Recent in-memory log entries. Query params: limit (1–1000, default 100), search (text filter). |
| GET | /logs/stream | Server-Sent Events (SSE) live log stream. Accepts ?token=<value> as an alternative to the Authorization header for browser EventSource clients. |
Database
| Method | Endpoint | Description |
|---|---|---|
| GET | /db/integrity | Run SQLite PRAGMA integrity_check. |
| GET | /db/backup | Download the current database as a .bak file using the SQLite Online Backup API. |
| POST | /db/vacuum | Run SQLite VACUUM to reclaim space. |
| POST | /db/rebuild-indexes | Run SQLite REINDEX. (/db/rebuild-indices is an alias.) |
| POST | /db/restore | Upload a SQLite backup file to stage a restore. File is validated before staging. The restore is applied on the next gateway restart. Requires Content-Length header. Size limited by DatabaseAdmin:MaxRestoreSizeMb. |
Certificates
| Method | Endpoint | Description |
|---|---|---|
| GET | /certs/current | Reports current HTTPS certificate status. Finds the first endpoint with an https:// URL (by scheme, not by name). Returns endpoint name, port, scheme, validity, expiry, thumbprint, SAN names, key type/size, and self-signed flag. |
| POST | /certs/validate | Validate a PFX/PKCS#12 file without installing it. Multipart form: file (the certificate), password (optional). Returns validation details including warnings for expiring or self-signed certificates. |
| POST | /certs/upload | Validate and save a PFX certificate as GatewayDefault. Saves to data/certs/<thumbprint>.pfx and writes the certificate entry to appsettings.Local.json. Does not automatically add an HTTPS endpoint — configure the desired endpoint separately via PATCH /config or the Settings page, then restart. Multipart form: file, password (optional). |
| POST | /certs/generate-self-signed | Generate a new self-signed RSA certificate, save it as GatewayDefault, and register it in configuration. Useful for testing or internal deployments. Multipart form (all optional): cn (Common Name, default SecurePACS-gateway), days (validity period 1–3650, default 365). The generated PFX has no password. Configure the HTTPS endpoint separately and restart to activate. |
Exposing the Dashboard to the Network
To access the dashboard from another machine, set StatusApi:AllowNonLoopbackPrefixes = true. It is strongly recommended to also set an admin password (via Settings → Security) and enable HTTPS before exposing the dashboard on a non-loopback interface. Restrict firewall access to trusted IP ranges.
Usage Scenarios
Theoretical deployment examples.
Goal: Minimum delay between scan completion and cloud availability.
- Config:
StudySettling:InactivityTimeoutset to 2 minutes. - Config:
UploadWorker:ParallelFileUploadsset to 4. - Behavior: Large CT studies (2000+ slices) begin uploading almost immediately after the last image arrives. High parallelism maximizes local fiber internet bandwidth.
Goal: Ensure 100% delivery even with daily outages.
- Config:
UploadWorker:MaxAttemptsset to 0 (infinite retries). - Config:
Storage:RetainUploadedStudiesForset to 14 days. - Behavior: The gateway acts as a reliable buffer. If the internet goes down, studies queue up locally. Once the connection is restored, the gateway systematically uploads the backlog while the staff continues scanning.
Goal: Keep raw data locally until a researcher verifies the cloud upload.
- Config:
Cleanup:Policyset toManualReview. - Behavior: Studies are uploaded to SecurePACS. Local files remain on disk. A researcher periodically checks the cloud, then calls
POST /studies/{uid}/approve-cleanupto release local disk space.
Goal: Allow IT staff to monitor the gateway dashboard from their office PCs without VPN.
- Config: Upload a valid TLS certificate via the Settings page (or generate a self-signed one for internal use). Add a
StatusApiHttpsendpoint in the Network settings pointing to the certificate, then restart. The plain HTTP endpoint on 11113 is kept for loopback health checks. - Config: Set
StatusApi:AllowNonLoopbackPrefixes = true. Set an admin password via Settings → Security. - Config: Open the HTTPS port (e.g. 11114) in the firewall for the allowed IP range only. Port 11113 (HTTP loopback) does not need to be opened externally.
- Behavior: The dashboard is served over HTTPS on port 11114 from any network interface. Users log in with the admin password; sessions last 8 hours. Plain HTTP remains on port 11113, loopback only, for local health checks.
Troubleshooting
Common issues and their resolutions.
Log Inspection
Logs are the first line of defense. Check data/logs/gateway.log for overall activity and data/logs/gateway.errors.log for failures. The dashboard Logs page provides a filterable view without opening files.
Common Issues
- Modality can't connect: Check that port 11112 (or your configured DICOM port) is open in Windows Firewall or Linux
iptables. Ensure the called AE Title matches the gateway's configuredDicom:AeTitle. - Studies stuck in Receiving: This is normal when
InactivityTimeoutis long and the modality is still sending. Check the Connections dashboard page to confirm whether the association is still active. - Upload failures — 404 Not Found: The
PublicKeyIdis invalid or does not exist on the SecurePACS server. Check the startup banner and the/statusAPI for a validation note. - Upload failures — 401 / 403: Incorrect
SecurePacs:BearerToken(if configured). Check the SecurePACS server logs for more details. - HTTPS not working after save: A restart is required to apply HTTPS changes. Use the dashboard's System → Restart button or your service manager. After restart, connect on the HTTPS port (e.g. 11114), not the original HTTP port. Also verify that the HTTPS endpoint referencing
GatewayDefaultwas saved to configuration (Settings → Network) before restarting. - Dashboard returns 401 / login loop: Either no password is configured (open mode, should not redirect) or your session expired. Open
/auth/statusto check. IfopenMode: false, log in again at/auth/login. If you forgot the password, usePOST /system/config/resetfrom loopback to delete the local settings and re-run setup. - Dashboard returns 403: All state-changing requests require the
X-Gateway-Admin: trueheader. Add it to your API call. - Login rate-limited (HTTP 429): More than 5 login attempts were made from the same IP within 15 minutes. Wait for the
Retry-After: 900period to pass. - Disk full: Ensure both
Cleanup:Enabled = trueandStorage:DeleteRawFilesAfterUpload = true. If usingManualReviewpolicy, studies must be individually approved. The dashboard Database → Vacuum can reclaim space from the SQLite file itself. - Database restore not applied: Staged restores are applied on the next gateway startup. Restart the service after uploading a restore file.
Privacy & Security
Protecting Patient Health Information (PHI).
Encryption Protocol
The gateway uses the SecurePACS Cryptographic Protocol:
- An ephemeral ECDH P-521 key pair is generated for each study upload.
- A 256-bit AES data key is generated using a cryptographically secure random number generator (
RandomNumberGenerator.Fill). - An ECDH shared secret is derived from the ephemeral private key and the server's P-521 public key.
- Each DICOM file is zstd-compressed (level 3) and AES-CBC encrypted with the data key before being transmitted.
- Study metadata (map, info) is also compressed and encrypted. Only the holder of the server-side private key can decrypt it.
Data at Rest
Raw DICOM files are stored in data/studies. These files contain PHI and are not encrypted at rest by the gateway itself. It is strongly recommended to use OS-level disk encryption (BitLocker on Windows, LUKS on Linux) to protect the data directory.
Dashboard Security
The status API defaults to loopback-only access. If you expose it to other network interfaces:
- Always set an admin password via the dashboard Settings → Security page (
POST /auth/change-password). - Always enable HTTPS to prevent credential interception (the session cookie is HttpOnly but still transmitted in plaintext over plain HTTP on non-loopback).
- Restrict firewall access to trusted IP ranges.
Compliance
While the gateway provides the technical tools for HIPAA/GDPR compliance, the end-user is responsible for ensuring the overall deployment (physical security, access controls, network security) meets regulatory requirements.
Technical Reference
Architecture and performance details.
- Runtime: .NET 8 ASP.NET Core WebApplication (Kestrel HTTP server).
- DICOM:
fo-dicomfor DIMSE services (C-ECHO, C-STORE). - Persistence: SQLite with WAL mode for metadata, queue, instances, associations, and upload attempts.
- Compression:
ZstdSharpfor DICOM file compression (level 3). - Cryptography: .NET built-in
ECDiffieHellman,Aes, andRandomNumberGenerator. - Logging: NLog with rolling file, console, and in-memory dashboard targets.
- HTTP Client:
IHttpClientFactoryfor SecurePACS communication. - Platforms: Windows x64/ARM64 and Linux x64/ARM64.
| Path | Purpose |
|---|---|
appsettings.json | Default configuration (do not edit). |
appsettings.Local.json | User overrides; written by wizard, dashboard, and PATCH /config. |
data/gateway.sqlite | SQLite database with study queue, instances, associations, and audit trail. |
data/studies/ | Raw DICOM files organized by StudyUID/SeriesUID/SOPInstanceUID.dcm. |
data/certs/ | PFX certificates uploaded via dashboard or API. |
data/logs/gateway.log | Rolling general log. |
data/logs/gateway.errors.log | Rolling warnings and errors only. |
data/logs/archive/ | Rotated/archived log files. Cleared by POST /system/logs/clear-archive. |
NLog.config | NLog target configuration (log paths, archive count, levels). |
Values based on a typical quad-core server with SSD storage.
| Metric | Observed Value |
|---|---|
| DICOM Receive Speed | ~30–50 MB/s (network/disk bound) |
| Encryption Overhead | <50 ms per instance |
| Memory Footprint | ~150–300 MB (depends on study size and buffer limits) |
| Database Capacity | Tested up to 1,000,000 instances |