diff --git a/.gitignore b/.gitignore
index 38ed7e158f9..8cb764e486e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,9 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
+# Claude configuration
+.claude/
+
# User-specific files
*.suo
*.user
diff --git a/Directory.Build.props b/Directory.Build.props
index 745654af745..2276d8ed0df 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -2,7 +2,7 @@
Copyright © 2013-$([System.DateTime]::Now.Year) Akka.NET Team
Akka.NET Team
- 1.5.34
+ 1.5.56
akkalogo.png
https://getakka.net/
Apache-2.0
@@ -50,7 +50,15 @@
true
- Placeholder for nightlies*
+ Akka.NET v1.5.56 is a patch release containing important bug fixes for Akka.Remote and Akka.Streams.
+
+**Bug Fixes:**
+
+* [Fix: Akka.Remote should not shutdown on invalid TLS traffic](https://github.com/akkadotnet/akka.net/pull/7952) - Fixes [issue #7938](https://github.com/akkadotnet/akka.net/issues/7938) where invalid traffic (like HTTP requests) hitting a TLS-enabled Akka.Remote port would cause the entire ActorSystem to shut down. Server now rejects invalid connections gracefully without terminating.
+
+* [fix(streams): prevent race condition in ChannelSource on channel completion](https://github.com/akkadotnet/akka.net/pull/7951) - Fixes [issue #7940](https://github.com/akkadotnet/akka.net/issues/7940) where a `NullReferenceException` could occur when completing a `ChannelWriter` while the stream is waiting for data. Added atomic flag to prevent race condition between `OnReaderComplete` and `OnValueRead` callbacks.
+
+To see the full set of changes in Akka.NET v1.5.56, click here: https://github.com/akkadotnet/akka.net/milestone/139?closed=1
@@ -72,4 +80,4 @@
true
snupkg
-
+
\ No newline at end of file
diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md
index 0012691cf0d..4d0dfc6253f 100644
--- a/RELEASE_NOTES.md
+++ b/RELEASE_NOTES.md
@@ -1,6 +1,189 @@
-#### 1.5.48 August 14th, 2025 ####
+#### 1.5.57-beta1 December 2nd, 2025 ####
-**Placeholder for nightly build**
+Akka.NET v1.5.57-beta1 is a beta release containing a significant new feature for structured/semantic logging.
+
+**New Features:**
+
+* [Add native semantic logging support with property extraction](https://github.com/akkadotnet/akka.net/pull/7955) - Fixes [issue #7932](https://github.com/akkadotnet/akka.net/issues/7932). This release adds comprehensive structured logging support to Akka.NET with both positional (`{0}`) and named (`{PropertyName}`) message template parsing, enabling seamless integration with modern logging frameworks like Serilog, NLog, and Microsoft.Extensions.Logging. Key capabilities include:
+ - New `LogMessage.PropertyNames` and `GetProperties()` APIs for property extraction
+ - `SemanticLogMessageFormatter` as the new default formatter
+ - Performance optimized with 75% allocation reduction compared to the previous implementation
+ - Zero new dependencies and fully backward compatible
+ - EventFilter support for semantic templates in unit tests
+
+1 contributor since release 1.5.56
+
+| COMMITS | LOC+ | LOC- | AUTHOR |
+| --- | --- | --- | --- |
+| 1 | 2317 | 14 | Aaron Stannard |
+
+To [see the full set of changes in Akka.NET v1.5.57-beta1, click here](https://github.com/akkadotnet/akka.net/milestone/140?closed=1)
+
+#### 1.5.56 November 25th, 2025 ####
+
+Akka.NET v1.5.56 is a patch release containing important bug fixes for Akka.Remote and Akka.Streams.
+
+**Bug Fixes:**
+
+* [Fix: Akka.Remote should not shutdown on invalid TLS traffic](https://github.com/akkadotnet/akka.net/pull/7952) - Fixes [issue #7938](https://github.com/akkadotnet/akka.net/issues/7938) where invalid traffic (like HTTP requests) hitting a TLS-enabled Akka.Remote port would cause the entire ActorSystem to shut down. Server now rejects invalid connections gracefully without terminating.
+
+* [fix(streams): prevent race condition in ChannelSource on channel completion](https://github.com/akkadotnet/akka.net/pull/7951) - Fixes [issue #7940](https://github.com/akkadotnet/akka.net/issues/7940) where a `NullReferenceException` could occur when completing a `ChannelWriter` while the stream is waiting for data. Added atomic flag to prevent race condition between `OnReaderComplete` and `OnValueRead` callbacks.
+
+1 contributor since release 1.5.55
+
+| COMMITS | LOC+ | LOC- | AUTHOR |
+| --- | --- | --- | --- |
+| 2 | 162 | 6 | Aaron Stannard |
+
+To [see the full set of changes in Akka.NET v1.5.56, click here](https://github.com/akkadotnet/akka.net/milestone/139?closed=1)
+
+#### 1.5.55 October 26th, 2025 ####
+
+Akka.NET v1.5.55 is a patch release containing important stability and security improvements for Akka.Remote.
+
+**Akka.Remote Stability Improvements:**
+
+* [Akka.Remote: harden EndpointWriter against serialization failures](https://github.com/akkadotnet/akka.net/pull/7925) - Fixes [issue #7922](https://github.com/akkadotnet/akka.net/issues/7922) by hardening the `EndpointWriter` against a broader range of potential serialization failures, improving overall remoting stability.
+
+**Akka.Remote Security Improvements:**
+
+* [Custom certificate validation with single execution path - fixes mTLS asymmetry bug](https://github.com/akkadotnet/akka.net/pull/7921) - Fixes [issue #7914](https://github.com/akkadotnet/akka.net/issues/7914) by introducing programmatic certificate validation helpers through the new `CertificateValidation` factory class. This release adds 7 new validation helper methods including `ValidateChain()`, `ValidateHostname()`, `PinnedCertificate()`, `ValidateSubject()`, `ValidateIssuer()`, `Combine()`, and `ChainPlusThen()`. The update also fixes an mTLS asymmetry bug where server-side hostname validation was not being applied consistently with client-side validation, all while maintaining full backward compatibility with existing HOCON-based validation.
+
+* [Fix DotNettySslSetup being ignored when HOCON has valid SSL config](https://github.com/akkadotnet/akka.net/pull/7919) - Fixes [issue #7917](https://github.com/akkadotnet/akka.net/issues/7917) where programmatic `DotNettySslSetup` settings were incorrectly being overridden by HOCON configuration. Programmatic configuration now correctly takes precedence over HOCON defaults as intended.
+
+1 contributor since release 1.5.54
+
+| COMMITS | LOC+ | LOC- | AUTHOR |
+| --- | --- | --- | --- |
+| 3 | 1605 | 289 | Aaron Stannard |
+
+
+To [see the full set of changes in Akka.NET v1.5.55, click here](https://github.com/akkadotnet/akka.net/milestone/138?closed=1)
+
+#### 1.5.54 October 17th, 2025 ####
+
+Akka.NET v1.5.54 is a patch release containing important bug fixes for Akka.Streams and Akka.DistributedData.
+
+**Bug Fixes:**
+
+* [Fix SourceRef.Source and SinkRef.Sink non-idempotent property bug](https://github.com/akkadotnet/akka.net/pull/7907) - Fixes [issue #7895](https://github.com/akkadotnet/akka.net/issues/7895) where `ISourceRef.Source` and `ISinkRef.Sink` properties created new stage instances on every access, causing race conditions and intermittent subscription timeouts. These properties are now idempotent using `Lazy`, preventing failures from accidental property access (debugger inspection, logging, serialization frameworks).
+
+* [Fix LWWDictionary.Delta ArgumentNullException when underlying delta is null](https://github.com/akkadotnet/akka.net/pull/7912) - Fixes [issue #7910](https://github.com/akkadotnet/akka.net/issues/7910) where `LWWDictionary.Delta` would throw `ArgumentNullException` when the underlying `ORDictionary.Delta` was `null`, which is a legitimate state after initialization or calling `ResetDelta()`.
+
+1 contributor since release 1.5.53
+
+| COMMITS | LOC+ | LOC- | AUTHOR |
+| --- | --- | --- | --- |
+| 2 | 159 | 20 | Aaron Stannard |
+
+
+To [see the full set of changes in Akka.NET v1.5.54, click here](https://github.com/akkadotnet/akka.net/milestone/137?closed=1)
+
+#### 1.5.53 October 9th, 2025 ####
+
+Akka.NET v1.5.53 is a security patch containing important fixes for TLS/SSL hostname validation and improved error diagnostics for certificate authentication issues.
+
+**Security Fixes:**
+
+* [Fix TLS hostname validation bug and add configurable validation](https://github.com/akkadotnet/akka.net/pull/7897) - Fixes a critical bug where TLS clients validated against their own certificate DNS name instead of the remote server address, particularly affecting mutual TLS scenarios. This release also adds a new `validate-certificate-hostname` configuration option to `akka.remote.dot-netty.tcp` (defaults to `false` for backward compatibility) and introduces type-safe validation APIs through the new `TlsValidationCallbacks` factory class.
+
+**Improvements:**
+
+* [Improve TLS/SSL certificate error messages during handshake failures](https://github.com/akkadotnet/akka.net/pull/7891) - Provides human-readable, actionable error messages for TLS/SSL certificate validation failures with detailed troubleshooting guidance, significantly improving the developer experience when configuring certificate-based authentication.
+
+1 contributor since release 1.5.52
+
+| COMMITS | LOC+ | LOC- | AUTHOR |
+| --- | --- | --- | --- |
+| 2 | 1060 | 77 | Aaron Stannard |
+
+
+To [see the full set of changes in Akka.NET v1.5.53, click here](https://github.com/akkadotnet/akka.net/milestone/136?closed=1)
+
+#### 1.5.52 October 6th, 2025 ####
+
+**SECURITY PATCH**
+
+Akka.NET v1.5.52 is a security patch containing crucial fixes for enforcing certificate-based authentication using mTLS enforcement. Please see https://getakka.net/articles/remoting/security.html for details on how this works.
+
+* [Akka.Remote: implement mutual TLS authentication support](https://github.com/akkadotnet/akka.net/pull/7851)
+* [Akka.Remote: validate SSL certificate private key access at server startup](https://github.com/akkadotnet/akka.net/pull/7847)
+
+Other fixes:
+
+* [Akka.Cluster.Sharding: ShardedDaemonSets: randomize starting worker index](https://github.com/akkadotnet/akka.net/pull/7857)
+
+1 contributors since release 1.5.51
+
+| COMMITS | LOC+ | LOC- | AUTHOR |
+| --- | --- | --- | --- |
+| 3 | 1193 | 149 | Aaron Stannard |
+
+
+To [see the full set of changes in Akka.NET v1.5.52, click here](https://github.com/akkadotnet/akka.net/milestone/135?closed=1)
+
+#### 1.5.51 October 1st, 2025 ####
+
+Akka.NET v1.5.51 is a minor patch containing a remoting bug fix and add required codes to support persistence health check.
+
+* [Remote: Fix DotNetty TLS handshake error handling](https://github.com/akkadotnet/akka.net/pull/7839)
+* [Persistence: Add health check handling code](https://github.com/akkadotnet/akka.net/pull/7842)
+
+2 contributors since release 1.5.50
+
+| COMMITS | LOC+ | LOC- | AUTHOR |
+|---------|------|------|---------------------|
+| 1 | 609 | 31 | Aaron Stannard |
+| 1 | 139 | 5 | Gregorius Soedharmo |
+
+To [see the full set of changes in Akka.NET v1.5.51, click here](https://github.com/akkadotnet/akka.net/milestone/134?closed=1)
+
+#### 1.5.50 September 22nd, 2025 ####
+
+Akka.NET v1.5.50 is a minor patch containing a bug fix.
+
+* [Remote: Propagate error from DotNetty TLS Handshake failure to Akka.Remote](https://github.com/akkadotnet/akka.net/pull/7824)
+
+1 contributor since release 1.5.49
+
+| COMMITS | LOC+ | LOC- | AUTHOR |
+|---------|------|------|---------------------|
+| 1 | 187 | 1 | Gregorius Soedharmo |
+
+To [see the full set of changes in Akka.NET v1.5.50, click here](https://github.com/akkadotnet/akka.net/milestone/133?closed=1)
+
+#### 1.5.49 September 10th, 2025 ####
+
+Akka.NET v1.5.49 is a minor patch containing several bug fixes.
+
+* [Core: Fix IIS/Windows Service console race condition](https://github.com/akkadotnet/akka.net/pull/7793)
+* [DData: Fix Replicator.ReceiveUnsubscribe boolean logic](https://github.com/akkadotnet/akka.net/pull/7809)
+* [Streams: Fix ConcurrentAsyncCallback with ChannelSource throws NRE](https://github.com/akkadotnet/akka.net/pull/7808)
+
+3 contributors since release 1.5.48
+
+| COMMITS | LOC+ | LOC- | AUTHOR |
+|---------|------|------|---------------------|
+| 18 | 6011 | 9343 | Aaron Stannard |
+| 18 | 3760 | 3880 | Gregorius Soedharmo |
+| 1 | 1 | 1 | dependabot[bot] |
+
+To [see the full set of changes in Akka.NET v1.5.49, click here](https://github.com/akkadotnet/akka.net/milestone/132?closed=1)
+
+#### 1.5.48 August 21st, 2025 ####
+
+Akka.NET v1.5.48 is a minor patch containing stability improvement to Akka.TestKit.
+
+* [TestKit: Fix deadlock during parallel test execution](https://github.com/akkadotnet/akka.net/pull/7787)
+
+2 contributors since release 1.5.47
+
+| COMMITS | LOC+ | LOC- | AUTHOR |
+|---------|------|------|---------------------|
+| 4 | 5494 | 5561 | Aaron Stannard |
+| 2 | 204 | 66 | Gregorius Soedharmo |
+
+To [see the full set of changes in Akka.NET v1.5.48, click here](https://github.com/akkadotnet/akka.net/milestone/131?closed=1)
#### 1.5.47 August 12th, 2025 ####
diff --git a/build-system/azure-pipeline.mntr-template.yaml b/build-system/azure-pipeline.mntr-template.yaml
index ec0d732cd13..f1c237867bc 100644
--- a/build-system/azure-pipeline.mntr-template.yaml
+++ b/build-system/azure-pipeline.mntr-template.yaml
@@ -20,6 +20,19 @@ jobs:
inputs:
packageType: 'sdk'
useGlobalJson: true
+
+ # Set the Incrementalist base branch based on PR target branch
+ - pwsh: |
+ if ('$(Build.Reason)' -eq 'PullRequest') {
+ # Extract branch name from refs/heads/branch format
+ $targetBranch = '$(System.PullRequest.TargetBranch)'.Replace('refs/heads/', '')
+ Write-Host "PR detected - using base branch: $targetBranch"
+ Write-Host "##vso[task.setvariable variable=IncrementalistBaseBranch]$targetBranch"
+ } else {
+ Write-Host "Not a PR - using default base branch: dev"
+ Write-Host "##vso[task.setvariable variable=IncrementalistBaseBranch]dev"
+ }
+ displayName: 'Set Incrementalist base branch'
- script: dotnet tool restore
displayName: 'Restore dotnet tools'
diff --git a/build-system/azure-pipeline.template.yaml b/build-system/azure-pipeline.template.yaml
index 7cde35b6587..09d1ee5d667 100644
--- a/build-system/azure-pipeline.template.yaml
+++ b/build-system/azure-pipeline.template.yaml
@@ -27,6 +27,19 @@ jobs:
inputs:
packageType: 'sdk'
useGlobalJson: true
+
+ # Set the Incrementalist base branch based on PR target branch
+ - pwsh: |
+ if ('$(Build.Reason)' -eq 'PullRequest') {
+ # Extract branch name from refs/heads/branch format
+ $targetBranch = '$(System.PullRequest.TargetBranch)'.Replace('refs/heads/', '')
+ Write-Host "PR detected - using base branch: $targetBranch"
+ Write-Host "##vso[task.setvariable variable=IncrementalistBaseBranch]$targetBranch"
+ } else {
+ Write-Host "Not a PR - using default base branch: dev"
+ Write-Host "##vso[task.setvariable variable=IncrementalistBaseBranch]dev"
+ }
+ displayName: 'Set Incrementalist base branch'
- script: dotnet tool restore
displayName: 'Restore dotnet tools'
diff --git a/build-system/pr-validation.yaml b/build-system/pr-validation.yaml
index 3e52eafc15f..1a49c58fd0c 100644
--- a/build-system/pr-validation.yaml
+++ b/build-system/pr-validation.yaml
@@ -59,7 +59,7 @@ jobs:
name: "netfx_tests_windows"
displayName: ".NET Framework Unit Tests (Windows)"
vmImage: "windows-latest"
- command: "dotnet incrementalist run --config .incrementalist/testsOnly.json -- test -c Release --no-build --framework net48 --logger:trx --results-directory TestResults"
+ command: "dotnet incrementalist run --config .incrementalist/testsOnly.json --branch $(IncrementalistBaseBranch) -- test -c Release --no-build --framework net48 --logger:trx --results-directory TestResults"
outputDirectory: "TestResults"
artifactName: "netfx_tests_windows-$(Build.BuildId)"
@@ -80,7 +80,7 @@ jobs:
name: "net_tests_windows"
displayName: ".NET Unit Tests (Windows)"
vmImage: "windows-latest"
- command: "dotnet incrementalist run --config .incrementalist/testsOnly.json -- test -c Release --no-build --framework net8.0 --logger:trx --results-directory TestResults"
+ command: "dotnet incrementalist run --config .incrementalist/testsOnly.json --branch $(IncrementalistBaseBranch) -- test -c Release --no-build --framework net8.0 --logger:trx --results-directory TestResults"
outputDirectory: "TestResults"
artifactName: "net_tests_windows-$(Build.BuildId)"
@@ -89,7 +89,7 @@ jobs:
name: "net_tests_linux"
displayName: ".NET Unit Tests (Linux)"
vmImage: "ubuntu-latest"
- command: "dotnet incrementalist run --config .incrementalist/testsOnly.json -- test -c Release --no-build --framework net8.0 --logger:trx --results-directory TestResults"
+ command: "dotnet incrementalist run --config .incrementalist/testsOnly.json --branch $(IncrementalistBaseBranch) -- test -c Release --no-build --framework net8.0 --logger:trx --results-directory TestResults"
outputDirectory: "TestResults"
artifactName: "net_tests_linux-$(Build.BuildId)"
@@ -98,7 +98,7 @@ jobs:
name: "net_mntr_windows"
displayName: ".NET Multi-Node Tests (Windows)"
vmImage: "windows-latest"
- command: "dotnet incrementalist run --config .incrementalist/mutliNodeOnly.json -- test -c Release --no-build --framework net8.0 --logger:trx --results-directory TestResults/multinode"
+ command: "dotnet incrementalist run --config .incrementalist/mutliNodeOnly.json --branch $(IncrementalistBaseBranch) -- test -c Release --no-build --framework net8.0 --logger:trx --results-directory TestResults/multinode"
outputDirectory: "TestResults"
artifactName: "net_mntr_windows-$(Build.BuildId)"
mntrFailuresDir: 'TestResults\\multinode'
diff --git a/build-system/windows-release.yaml b/build-system/windows-release.yaml
index de6187147a4..29d11345e8f 100644
--- a/build-system/windows-release.yaml
+++ b/build-system/windows-release.yaml
@@ -3,7 +3,6 @@
pool:
vmImage: windows-latest
- demands: Cmd
trigger:
branches:
@@ -45,4 +44,4 @@ steps:
title: '$(projectName) v$(Build.SourceBranchName)'
releaseNotesFile: 'RELEASE_NOTES.md'
assets: |
- $(Build.ArtifactStagingDirectory)/nuget/*.nupkg
\ No newline at end of file
+ $(Build.ArtifactStagingDirectory)/nuget/*.nupkg
diff --git a/docs/articles/remoting/security.md b/docs/articles/remoting/security.md
index 19f7a18d50f..c83663a3e07 100644
--- a/docs/articles/remoting/security.md
+++ b/docs/articles/remoting/security.md
@@ -5,52 +5,794 @@ title: Network Security
# Akka.Remote Security
-There are 2 ways you may like to achieve network security when using Akka.Remote:
+## Important Context: When You Need TLS
-* Transport Layer Security (introduced with Akka.Remote Version 1.2)
-* Virtual Private Networks
+**Akka.Remote is designed for internal cluster communication and should NOT be exposed to the public internet.** Most Akka.NET deployments run within:
-## Akka.Remote with TLS (Transport Layer Security)
+* Private networks (VPNs, VPCs)
+* Internal data centers
+* Kubernetes clusters with network policies
+* Behind firewalls with strict ingress rules
-The release of Akka.NET version 1.2.0 introduces the default [DotNetty](https://github.com/Azure/DotNetty) transport and the ability to configure [TLS](http://en.wikipedia.org/wiki/Transport_Layer_Security) security across Akka.Remote Actor Systems. In order to use TLS, you must first install a valid SSL certificate on all Akka.Remote hosts that you intend to use TLS.
+### When TLS Is Optional
-Once you've installed valid SSL certificates, TLS is enabled via your HOCON configuration by setting `enable-ssl = true` and configuring the `ssl` HOCON configuration section like below:
+For many deployments, TLS is not strictly necessary:
+
+* **Internal networks only** - If your cluster runs entirely within a trusted network boundary
+* **Development/staging environments** - Where data sensitivity is low
+* **Kubernetes with network policies** - Where the container network provides isolation
+
+### When TLS Is Recommended
+
+You should enable TLS when:
+
+* **Crossing network boundaries** - Communication between data centers or cloud regions
+* **Public internet transit** - Any traffic over public networks (even with VPN)
+* **Compliance requirements** - PCI-DSS, HIPAA, or other regulatory needs
+* **Defense-in-depth** - Additional security layer even on private networks
+* **Multi-tenant environments** - Shared infrastructure with other applications
+
+## Security Layers
+
+Akka.Remote security operates on three complementary layers:
+
+1. **Network Isolation** - Using VPNs or private networks to restrict which machines can reach your actor systems
+2. **Transport Encryption** - Using TLS to encrypt all communication between nodes
+3. **Authentication** - Using mutual TLS to verify the identity of all connecting nodes
+
+You should use **all three layers** in production for defense-in-depth security.
+
+## TLS (Transport Layer Security) Overview
+
+TLS encryption was introduced in Akka.NET v1.2 with the DotNetty transport. It provides:
+
+**What TLS Protects Against:**
+
+* Eavesdropping (all messages are encrypted)
+* Man-in-the-middle attacks (certificates verify server identity)
+* Network packet injection (cryptographic integrity checks)
+
+**What TLS Does NOT Protect Against:**
+
+* Misconfigured certificates (see startup validation below)
+* Compromised private keys (rotate certificates regularly)
+* Application-level authorization (implement this separately)
+
+## Certificate Validation: Independent Control
+
+**New in Akka.NET v1.5.52+:** Certificate validation is now split into two independent settings for greater flexibility.
+
+### Two Types of Validation
+
+1. **Chain Validation** (`suppress-validation`) - Validates certificate against trusted CAs
+2. **Hostname Validation** (`validate-certificate-hostname`) - Validates certificate CN/SAN matches target hostname
+
+These settings are **independent** and can be configured separately based on your deployment scenario.
+
+### Chain Validation
+
+The `suppress-validation` setting controls whether the certificate chain is validated against trusted root CAs.
+
+**Default Certificate Stores Used:**
+
+When `suppress-validation = false`, .NET's `SslStream` validates certificates against the operating system's trusted root certificate stores:
+
+* **Windows**: Uses the [Windows Certificate Store](https://learn.microsoft.com/en-us/windows-hardware/drivers/install/local-machine-and-current-user-certificate-stores) - specifically the `Trusted Root Certification Authorities` store
+* **Linux**: Uses the system's CA bundle (typically `/etc/ssl/certs/ca-certificates.crt` or `/etc/pki/tls/certs/ca-bundle.crt`)
+* **macOS**: Uses the Keychain Access Trusted Certificates
+
+The validation process follows [RFC 5280 (X.509 PKI Certificate and CRL Profile)](https://datatracker.ietf.org/doc/html/rfc5280) and [RFC 6125 (Service Identity Verification)](https://datatracker.ietf.org/doc/html/rfc6125).
+
+#### Enabled (Recommended)
+
+When `suppress-validation = false` (the default when SSL is enabled):
+
+**What it validates:**
+
+* Certificate chain against system trusted root CAs
+* Certificate expiration dates
+* Certificate hasn't been revoked (if CRL/OCSP configured)
+
+**Does NOT validate:**
+
+* Hostname matching (see Hostname Validation section below)
+
+**When to use:** Always in production and any networked environment.
+
+#### Disabled (Use With Caution)
+
+When `suppress-validation = true`:
+
+**What it skips:**
+
+* Certificate chain validation (accepts self-signed certificates)
+* Expiration date checks
+* CA trust checks
+
+**When it's acceptable:**
+
+* Local development on `localhost` only
+* Automated testing with self-signed test certificates
+* Initial TLS setup/debugging before obtaining proper certificates
+
+**When it's NOT acceptable:**
+
+* Any production environment
+* Any network-accessible environment (dev, staging, QA)
+* Any environment processing sensitive data
+* Any multi-tenant environment
+
+### Validation Strategies: HOCON vs Programmatic (v1.5.52+)
+
+Two independent validation decisions determine your TLS security posture:
+
+1. **Chain Validation** - Verify certificate against trusted CAs (`suppress-validation`)
+2. **Hostname Validation** - Verify certificate CN/SAN matches target (`validate-certificate-hostname`)
+3. **Mutual Authentication** - Require both sides authenticate (`require-mutual-authentication`)
+
+#### Decision Matrix: Which Combination to Use
+
+| Use Case | suppress-validation | validate-hostname | mutual-auth | Config Approach |
+|----------|---------------------|-------------------|-------------|-----------------|
+| **P2P Cluster (Default)** | `false` | `false` | `true` | HOCON ✓ or Programmatic |
+| **Client-Server with Shared Cert** | `false` | `true` | `true` | HOCON ✓ or Programmatic |
+| **Development/Testing** | `true` | `false` | `false` | HOCON only |
+| **Certificate Pinning** | `false` | `false` | `true` | **Programmatic required** |
+| **Custom Subject/Issuer Validation** | `false` | `false` | `true` | **Programmatic required** |
+
+#### HOCON Configuration Approach
+
+When `validate-certificate-hostname = false` (the default):
+
+* Skips hostname validation
+* Only validates certificate chain (if `suppress-validation = false`)
+* **Best for:** Mutual TLS with per-node certificates, IP-based connections, Kubernetes dynamic discovery
+
+When `validate-certificate-hostname = true`:
+
+* Certificate CN (Common Name) or SAN (Subject Alternative Name) must match the target hostname
+* Traditional TLS hostname validation as used in HTTPS
+* **Best for:** Client-server architectures with shared certificates and stable DNS names
+
+**HOCON Example - P2P Cluster (Common Default):**
+
+```hocon
+akka.remote.dot-netty.tcp {
+ enable-ssl = true
+ ssl {
+ suppress-validation = false # Validate CA chain
+ require-mutual-authentication = true # Both sides authenticate
+ validate-certificate-hostname = false # Default: Allow per-node certs
+ certificate {
+ use-thumbprint-over-file = true
+ thumbprint = "2531c78c51e5041d02564697a88af8bc7a7ce3e3"
+ }
+ }
+}
+```
+
+**HOCON Example - Client-Server with Hostname Validation:**
+
+```hocon
+akka.remote.dot-netty.tcp {
+ enable-ssl = true
+ ssl {
+ suppress-validation = false # Validate CA chain
+ require-mutual-authentication = true # Both sides authenticate
+ validate-certificate-hostname = true # Hostname must match
+ certificate {
+ use-thumbprint-over-file = true
+ thumbprint = "2531c78c51e5041d02564697a88af8bc7a7ce3e3"
+ }
+ }
+}
+```
+
+#### Programmatic Configuration Approach
+
+Use `DotNettySslSetup` with `CertificateValidation` helpers when you need:
+
+* **Certificate pinning** - Accept only specific certificates
+* **Subject/Issuer validation** - Custom certificate attribute checks
+* **Custom business logic** - Domain-specific validation rules
+* **Dynamic validation** - Load rules from runtime sources
+
+See [Programmatic Certificate Validation](#programmatic-certificate-validation-v1555) below for detailed examples.
+
+### Self-Signed Certificates: The Right Way
+
+If you must use self-signed certificates (development/testing):
+
+#### Option 1: Trust the Self-Signed CA (Better)
+
+```powershell
+# Generate self-signed CA
+$ca = New-SelfSignedCertificate -Subject "CN=Dev-CA" -CertStoreLocation Cert:\CurrentUser\My -KeyUsage CertSign
+
+# Export and import to Trusted Root
+Export-Certificate -Cert $ca -FilePath dev-ca.cer
+Import-Certificate -FilePath dev-ca.cer -CertStoreLocation Cert:\LocalMachine\Root
+
+# Generate server cert signed by CA
+New-SelfSignedCertificate -Subject "CN=localhost" -Signer $ca -CertStoreLocation Cert:\LocalMachine\My
+```
+
+**Configuration:**
+
+```hocon
+akka.remote.dot-netty.tcp.ssl {
+ suppress-validation = false # ✓ Still validates, but trusts your CA
+ certificate {
+ use-thumbprint-over-file = true
+ thumbprint = "server-cert-thumbprint"
+ }
+}
+```
+
+**Pros:**
+
+* Maintains validation checks
+* Catches expiration/configuration errors
+* More realistic test environment
+
+#### Option 2: Suppress Validation (Quick but Dangerous)
+
+```hocon
+akka.remote.dot-netty.tcp.ssl {
+ suppress-validation = true # ⚠️ Development ONLY
+ certificate {
+ path = "self-signed.pfx"
+ password = "password"
+ }
+}
+```
+
+**Pros:**
+
+* Quick setup
+* No certificate installation needed
+
+**Cons:**
+
+* Doesn't catch real configuration errors
+* False sense of security
+* Easy to accidentally deploy to production
+
+**WARNING:** Never commit `suppress-validation = true` to version control for production configs. Use environment-specific configuration files.
+
+## Certificate Configuration
+
+### Option 1: Certificate File (Recommended for Development)
```hocon
-akka {
- loglevel = DEBUG
- actor {
- provider = remote
+akka.remote.dot-netty.tcp {
+ enable-ssl = true
+ ssl {
+ suppress-validation = false # IMPORTANT: Never use true in production!
+ certificate {
+ path = "path/to/certificate.pfx"
+ password = "certificate-password"
+ # Optional: Specify key storage flags
+ flags = [ "exportable" ]
+ }
}
- remote {
- dot-netty.tcp {
- port = 0
- hostname = 127.0.0.1
- enable-ssl = true
- log-transport = true
- ssl {
- suppress-validation = true
- certificate {
- # valid ssl certificate must be installed on both hosts
- path = ""
- password = ""
- # flags is optional: defaults to "default-flag-set" key storage flag
- # other available storage flags:
- # exportable | machine-key-set | persist-key-set | user-key-set | user-protected
- flags = [ "default-flag-set" ]
- }
- }
+}
+```
+
+**When to use:** Development, testing, containerized environments where you can mount certificate files.
+
+**Pros:**
+
+* Easy to deploy with containers
+* Simple to version control (store path, not certificate)
+* Works well with configuration management tools
+
+**Cons:**
+
+* Certificate files can be copied if filesystem is compromised
+* Requires file system access for certificate deployment
+
+### Option 2: Windows Certificate Store (Recommended for Production)
+
+```hocon
+akka.remote.dot-netty.tcp {
+ enable-ssl = true
+ ssl {
+ suppress-validation = false
+ certificate {
+ use-thumbprint-over-file = true
+ thumbprint = "2531c78c51e5041d02564697a88af8bc7a7ce3e3"
+ store-name = "My"
+ store-location = "local-machine" # or "current-user"
}
}
}
```
-## Akka.Remote with Virtual Private Networks
+**When to use:** Windows production environments, enterprise deployments with centralized certificate management.
+
+**Pros:**
+
+* Leverages Windows ACL for private key protection
+* Integrates with enterprise PKI infrastructure
+* Supports hardware security modules (HSM)
+* Private keys can be marked as non-exportable
+
+**Cons:**
+
+* Windows-specific (not portable to Linux)
+* Requires administrative access for certificate installation
+* More complex initial setup
+
+**Finding Your Thumbprint:**
+
+1. Open `certlm.msc` (Local Machine) or `certmgr.msc` (Current User)
+2. Navigate to Personal > Certificates
+3. Double-click your certificate
+4. Go to Details tab
+5. Scroll to Thumbprint field
+6. Copy the value (remove spaces)
+
+## Programmatic Certificate Validation (v1.5.55+)
+
+**New in Akka.NET v1.5.55:** Certificate validation can now be configured programmatically using `DotNettySslSetup` with custom validators. This provides fine-grained control over validation logic while maintaining full backward compatibility with HOCON configuration.
+
+### When to Use Programmatic Configuration
+
+Use programmatic setup when you need:
+
+* **Custom validation logic** - Implement domain-specific validation rules
+* **Certificate pinning** - Accept only specific certificates by thumbprint
+* **Subject/Issuer validation** - Verify certificate attributes
+* **Dynamic configuration** - Load validation rules from runtime sources
+* **Composable validators** - Combine multiple validation strategies
+
+### CertificateValidation Helper Factory
+
+The `CertificateValidation` static class provides 7 helper methods for common validation patterns:
+
+#### Basic Chain Validation
+
+[!code-csharp[ProgrammaticMutualTlsSetup](../../../src/core/Akka.Docs.Tests/Configuration/TlsConfigurationSample.cs?name=ProgrammaticMutualTlsSetup)]
+
+#### Certificate Pinning by Thumbprint
+
+Accept only certificates with specific thumbprints. Prevents man-in-the-middle attacks if CA is compromised:
+
+[!code-csharp[CertificatePinningExample](../../../src/core/Akka.Docs.Tests/Configuration/TlsConfigurationSample.cs?name=CertificatePinningExample)]
+
+#### Custom Validation Logic with ChainPlusThen
+
+Perform standard chain validation, then apply custom business logic:
+
+[!code-csharp[CustomValidationLogicExample](../../../src/core/Akka.Docs.Tests/Configuration/TlsConfigurationSample.cs?name=CustomValidationLogicExample)]
+
+#### Hostname Validation
+
+Enable traditional TLS hostname validation (certificate CN/SAN must match target hostname). Use for client-server architectures with shared certificates:
+
+[!code-csharp[HostnameValidationExample](../../../src/core/Akka.Docs.Tests/Configuration/TlsConfigurationSample.cs?name=HostnameValidationExample)]
+
+#### Subject DN Validation
+
+Accept only certificates with specific subject names:
+
+[!code-csharp[SubjectValidationExample](../../../src/core/Akka.Docs.Tests/Configuration/TlsConfigurationSample.cs?name=SubjectValidationExample)]
+
+### CertificateValidation Helper Methods
+
+| Method | Purpose |
+|--------|---------|
+| `ValidateChain()` | CA chain validation with full error details |
+| `ValidateHostname()` | Traditional TLS hostname validation (CN/SAN matching) |
+| `PinnedCertificate()` | Certificate pinning by thumbprint whitelist |
+| `ValidateSubject()` | Subject DN pattern matching (e.g., CN, O, OU) |
+| `ValidateIssuer()` | Issuer DN pattern matching |
+| `Combine()` | Compose multiple validators (AND logic) |
+| `ChainPlusThen()` | Chain validation + custom business logic |
+
+### Custom Validator Precedence
+
+When both custom validators and HOCON config are present, custom validators take precedence:
+
+```csharp
+// This validator will be used regardless of HOCON suppress-validation setting
+var customValidator = CertificateValidation.ValidateChain(log);
+var sslSetup = new DotNettySslSetup(
+ certificate: cert,
+ suppressValidation: false, // Ignored when customValidator provided
+ customValidator: customValidator
+);
+```
+
+This ensures programmatic validation logic always takes priority for explicit security requirements.
+
+## Startup Certificate Validation (v1.5.52+)
-The absolute best practice for securing remote Akka.NET applications today is to make the network around the applications secure - don't use public, open networks! Instead, use a private network to restrict machines that can contact Akka.Remote processes to ones who have your VPN credentials.
+**New in Akka.NET v1.5.52:** The transport now validates certificate configuration at startup, preventing runtime failures.
+
+### What It Validates
+
+The startup validation verifies:
+
+* Certificate exists in the specified location
+* Certificate has a private key associated
+* Application has permissions to access the private key
+* Private key is accessible for both RSA and ECDSA algorithms
+
+This fail-fast validation prevents runtime TLS handshake failures by detecting certificate configuration problems during system initialization.
+
+### Common Private Key Permission Issues
+
+**Symptom:** "SSL certificate private key exists but cannot be accessed"
+
+**Cause:** Application user lacks permissions to the private key file in Windows certificate store.
+
+**Solution:** Grant private key access to your application user:
+
+```powershell
+# Find the certificate
+$cert = Get-ChildItem Cert:\LocalMachine\My | Where-Object {$_.Thumbprint -eq "YOUR_THUMBPRINT"}
+
+# Get private key file location
+$keyPath = $cert.PrivateKey.CspKeyContainerInfo.UniqueKeyContainerName
+$keyFullPath = "C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys\$keyPath"
+
+# Grant read permissions
+$acl = Get-Acl $keyFullPath
+$permission = "DOMAIN\AppUser","Read","Allow"
+$accessRule = New-Object System.Security.AccessControl.FileSystemAccessRule $permission
+$acl.AddAccessRule($accessRule)
+Set-Acl $keyFullPath $acl
+```
+
+## Understanding Mutual TLS (mTLS) vs Standard TLS (v1.5.52+)
+
+Akka.NET supports both standard TLS and mutual TLS (mTLS), configured via the `require-mutual-authentication` setting in the [Validation Strategies](#validation-strategies-hocon-vs-programmatic-v1552) section above.
+
+### Visual Comparison
+
+**Standard TLS (Server Authentication Only):**
+
+```mermaid
+sequenceDiagram
+ participant Client
+ participant Server
+
+ Client->>Server: Connect (no certificate)
+ Server->>Client: Send server certificate
+ Client->>Client: Validate server certificate
+ Client->>Server: Accept connection
+ Note over Client,Server: Encrypted communication established
+```
+
+**Mutual TLS (Client + Server Authentication):**
+
+```mermaid
+sequenceDiagram
+ participant Client
+ participant Server
+
+ Client->>Server: Connect with client certificate
+ Server->>Client: Send server certificate
+ Client->>Client: Validate server certificate
+ Server->>Server: Validate client certificate
+ Client->>Server: Accept connection
+ Server->>Client: Accept connection
+ Note over Client,Server: Mutually authenticated encryption established
+```
+
+### When to Enable Mutual TLS
+
+**Enable mutual TLS (`require-mutual-authentication = true`) when:**
+
+* All nodes are under your control (typical Akka.NET cluster) ✓ **Recommended**
+* You need defense-in-depth security
+* Compliance requires bidirectional authentication (PCI-DSS, HIPAA, etc.)
+* You want to prevent misconfigured nodes from joining
+
+**Disable mutual TLS (`require-mutual-authentication = false`) when:**
+
+* Clients cannot provide certificates (rare in Akka.NET)
+* You're using client-server architecture where clients are untrusted
+* Backward compatibility with older clients required
+
+**Default is TRUE for security-by-default posture** (since v1.5.52).
+
+### Security Benefits of Mutual TLS
+
+1. **Prevents Asymmetric Connectivity Issues**
+ * Without mTLS: A node with broken certificate can connect OUT to cluster (client TLS succeeds)
+ * With mTLS: Node cannot connect without working certificate (enforced both ways)
+
+2. **Defense-in-Depth**
+ * Startup validation prevents broken servers
+ * Mutual TLS prevents broken clients
+ * Both together provide complete protection
+
+3. **Identity Verification**
+ * Every node must prove it owns the certificate
+ * Prevents certificate theft attacks (attacker needs private key)
+
+For configuration examples in both HOCON and programmatic styles, see [Validation Strategies](#validation-strategies-hocon-vs-programmatic-v1552) and [Programmatic Certificate Validation](#programmatic-certificate-validation-v1555) sections above.
+
+## Configuration Examples and Security Analysis
+
+This section provides concrete examples of different security configurations and their tradeoffs.
+
+### HOCON Configuration Security Levels
+
+**Development/Testing Only (INSECURE):**
+
+[!code-csharp[DevTlsConfig](../../../src/core/Akka.Docs.Tests/Configuration/TlsConfigurationSample.cs?name=DevTlsConfig)]
+
+* ⚠️ `suppress-validation = true` accepts ANY certificate (self-signed, expired, invalid chains)
+* Vulnerable to man-in-the-middle attacks
+* No client authentication
+* **Use only:** Local development, never in networked environments
+
+**Standard TLS (Medium-High Security):**
+
+[!code-csharp[StandardTlsConfig](../../../src/core/Akka.Docs.Tests/Configuration/TlsConfigurationSample.cs?name=StandardTlsConfig)]
+
+* Server proves identity to clients
+* All traffic encrypted
+* Startup validation prevents misconfigurations
+* **Use when:** Mutual TLS is not feasible
+
+**Mutual TLS with Windows Certificate Store (Maximum Security - RECOMMENDED):**
+
+[!code-csharp[WindowsCertStoreConfig](../../../src/core/Akka.Docs.Tests/Configuration/TlsConfigurationSample.cs?name=WindowsCertStoreConfig)]
+
+* ✓ Both client and server prove identity
+* ✓ All traffic encrypted
+* ✓ Prevents misconfigured nodes from connecting
+* ✓ Private keys protected by Windows ACL
+* **Use when:** Production Akka.NET clusters (default recommended configuration)
+
+**Mutual TLS for P2P Clusters with Per-Node Certificates:**
+
+Refer to the [Validation Strategies](#validation-strategies-hocon-vs-programmatic-v1552) section for HOCON example showing P2P cluster setup.
+
+**Client-Server with Hostname Validation:**
+
+Refer to the [Validation Strategies](#validation-strategies-hocon-vs-programmatic-v1552) section for HOCON example with hostname validation enabled.
+
+### Programmatic Configuration Security Levels
+
+For certificate pinning, subject/issuer validation, or custom logic, use programmatic setup:
+
+[!code-csharp[ProgrammaticMutualTlsSetup](../../../src/core/Akka.Docs.Tests/Configuration/TlsConfigurationSample.cs?name=ProgrammaticMutualTlsSetup)]
+
+[!code-csharp[CertificatePinningExample](../../../src/core/Akka.Docs.Tests/Configuration/TlsConfigurationSample.cs?name=CertificatePinningExample)]
+
+See [Programmatic Certificate Validation](#programmatic-certificate-validation-v1555) section for more examples.
+
+## Untrusted Mode
+
+In addition to TLS, Akka.Remote supports "untrusted mode" which prevents clients from sending system-level messages:
+
+```hocon
+akka.remote {
+ untrusted-mode = true
+
+ # Whitelist specific actors that can receive remote messages
+ trusted-selection-paths = [
+ "/user/api-handler",
+ "/user/public-endpoint"
+ ]
+}
+```
+
+**When to enable:**
+
+* You're exposing Akka.Remote to untrusted clients
+* You want to prevent remote actor creation/supervision
+* Defense against malicious remote commands
+
+**Note:** This does NOT replace TLS encryption. Use both together.
+
+## Virtual Private Networks (VPNs)
+
+The best practice for network security is to make the network itself secure. Run Akka.Remote on private networks that require VPN access.
+
+**Why VPNs matter:**
+
+* Restricts who can even attempt to connect
+* Provides network-level access control
+* Adds authentication layer before TLS
+* Protects against network scanning/discovery
+
+### VPN Options
+
+**Self-Hosted:**
+
+* [WireGuard](https://www.wireguard.com/) - Modern, fast, simple to configure
+* [OpenVPN](https://openvpn.net/) - Mature, widely supported
+
+**Cloud Provider VPNs:**
+
+* [AWS Virtual Private Cloud (VPC)](https://aws.amazon.com/vpc/)
+* [Azure Virtual Networks (VNet)](https://azure.microsoft.com/en-us/services/virtual-network/)
+* [Google Cloud VPC](https://cloud.google.com/vpc)
+
+**Managed Solutions:**
+
+* [Tailscale](https://tailscale.com/) - Zero-config VPN mesh networking
+* [ZeroTier](https://www.zerotier.com/) - Software-defined networking
+
+## Troubleshooting
+
+### Error: "SSL Certificate Private Key Exists but Cannot Be Accessed"
+
+**Cause:** Application lacks permissions to private key file.
+
+**Fix:** Run PowerShell script above to grant permissions.
+
+### Error: "The Remote Certificate Is Invalid According to the Validation Procedure"
+
+**Cause:** Certificate validation failed (expired, wrong CA, hostname mismatch).
+
+**Fix:**
+
+* Verify certificate is not expired: `Get-ChildItem Cert:\LocalMachine\My`
+* Check certificate CN/SAN matches hostname
+* For testing only: Set `suppress-validation = true` to identify if it's a validation issue
+
+### Error: "TLS Handshake Failed" with No Client Certificate
+
+**Cause:** Server requires mutual TLS but client didn't provide certificate.
+
+**Fix:**
+
+* Ensure all nodes have `require-mutual-authentication` set consistently
+* Verify client certificate is configured correctly
+* Check client application has private key access
+
+### Error: "RemoteCertificateNameMismatch" - Hostname Validation Failure
+
+**Full error message:**
+
+```text
+TLS certificate validation failed (full validation):
+ - Certificate name mismatch
+ - RemoteCertificateNameMismatch: The hostname being connected to does not match
+ the hostname(s) on the server certificate.
+
+Certificate Details:
+ Subject: CN=node1.example.com
+ Issuer: CN=My-CA
+ Valid: 2025-01-01 to 2026-01-01
+
+Connection target: 192.168.1.100:4053
+```
+
+**Cause:** Certificate CN/SAN doesn't match the target hostname/IP address.
+
+**Common scenarios:**
+
+1. **Connecting via IP but certificate has DNS name**
+ * Connecting to: `192.168.1.100`
+ * Certificate CN: `node1.example.com`
+
+2. **Per-node certificates in P2P cluster**
+ * Node A cert CN: `node-a.cluster.local`
+ * Node B cert CN: `node-b.cluster.local`
+ * Each node's certificate doesn't match the other node's hostname
+
+**Fix:**
+
+Option 1 (Recommended for P2P clusters): Disable hostname validation
+
+```hocon
+akka.remote.dot-netty.tcp.ssl {
+ validate-certificate-hostname = false # Allow per-node certs
+}
+```
+
+Option 2: Use certificates with matching CN/SAN
+
+```bash
+# Ensure certificate CN matches connection target
+# For IP connections, add IP SAN to certificate:
+New-SelfSignedCertificate -Subject "CN=node1" `
+ -DnsName "node1", "node1.example.com" `
+ -TextExtension @("2.5.29.17={text}IPAddress=192.168.1.100")
+```
+
+Option 3: Connect via DNS names that match certificate CN
+
+```hocon
+akka.remote.dot-netty.tcp {
+ hostname = "node1.example.com" # Must match cert CN
+}
+```
+
+### Error: "UntrustedRoot" - Certificate Chain Validation Failure
+
+**Full error message:**
+
+```text
+TLS/SSL certificate validation failed:
+ - Certificate chain validation errors
+ - UntrustedRoot: A certificate chain processed, but terminated in a root
+ certificate which is not trusted by the trust provider.
+
+Certificate Details:
+ Subject: CN=localhost
+ Issuer: CN=localhost (self-signed)
+```
+
+**Cause:** Certificate is self-signed or signed by untrusted CA.
+
+**Fix:**
+
+Option 1 (Development only): Suppress chain validation
+
+```hocon
+akka.remote.dot-netty.tcp.ssl {
+ suppress-validation = true # WARNING: Development only!
+}
+```
+
+Option 2 (Recommended): Trust the CA certificate
+
+```powershell
+# Windows: Import CA to Trusted Root store
+Import-Certificate -FilePath ca.cer -CertStoreLocation Cert:\LocalMachine\Root
+
+# Linux: Add to system CA bundle
+sudo cp ca.crt /usr/local/share/ca-certificates/
+sudo update-ca-certificates
+```
+
+### Understanding TLS Error Messages (v1.5.52+)
+
+Since v1.5.52, TLS handshake failures provide detailed diagnostic information including:
+
+* **Error category** (chain validation, hostname mismatch, etc.)
+* **Specific SSL policy error** with explanation
+* **Certificate details** (subject, issuer, validity period)
+* **Connection context** (local/remote addresses)
+* **Actionable recommendations**
+
+**Example comprehensive error:**
+
+```text
+TLS handshake failed on channel [127.0.0.1:4053->127.0.0.1:54321](Id=...)
+
+Detailed TLS Error:
+ - Certificate chain validation errors
+ - UntrustedRoot: A certificate chain processed, but terminated in a root
+ certificate which is not trusted by the trust provider.
+ - Certificate name mismatch
+ - RemoteCertificateNameMismatch: The hostname being connected to does not
+ match the hostname(s) on the server certificate.
+
+Certificate Information:
+ Subject: CN=node-test
+ Issuer: CN=node-test (self-signed)
+ Serial Number: 1A2B3C4D5E6F
+ Valid From: 2025-01-01 00:00:00 UTC
+ Valid To: 2026-01-01 00:00:00 UTC
+ Thumbprint: 2531c78c51e5041d02564697a88af8bc7a7ce3e3
+
+Recommendations:
+ - For development: Set 'suppress-validation = true' (testing only!)
+ - For production: Install certificate in trusted root store
+ - For hostname issues: Set 'validate-certificate-hostname = false' if using
+ per-node certificates or IP-based connections
+```
+
+## Additional Resources
+
+* [Windows Firewall Configuration Best Practices](https://learn.microsoft.com/en-us/windows/security/operating-system-security/network-security/windows-firewall/best-practices-configuring)
+* [TLS 1.2 Specification (RFC 5246)](https://datatracker.ietf.org/doc/html/rfc5246)
+* [OWASP Transport Layer Security Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Transport_Layer_Security_Cheat_Sheet.html)
+
+---
-Some options for doing this:
+**Related:**
-* [OpenVPN](https://openvpn.net/) - for "do it yourself" environments;
-* [Azure Virtual Networks](http://azure.microsoft.com/en-us/services/virtual-network/) - for Windows Azure customers; and
-* [Amazon Virtual Private Cloud (VPC)](http://aws.amazon.com/vpc/) - for Amazon Web Services customers.
+* [Akka.Remote Configuration](xref:akka-remote-configuration)
+* [DotNetty Transport](https://github.com/Azure/DotNetty)
diff --git a/docs/cSpell.json b/docs/cSpell.json
index 6a65e8176a9..706e9451c8b 100644
--- a/docs/cSpell.json
+++ b/docs/cSpell.json
@@ -36,6 +36,7 @@
"Hasher",
"Hipsterize",
"HOCON",
+ "hostnames",
"journaled",
"Kubernetes",
"lifecycles",
@@ -70,6 +71,7 @@
"Stannard",
"substream",
"substreams",
+ "Tailscale",
"testkit",
"threadedness",
"threadpool",
@@ -83,7 +85,8 @@
"userspace",
"watchee",
"Webcrawler",
- "Xunit"
+ "Xunit",
+ "ZeroTier"
],
"ignoreWords": [
"Hanselminutes",
diff --git a/scripts/contributors.sh b/scripts/contributors.sh
old mode 100644
new mode 100755
diff --git a/src/benchmark/Akka.Benchmarks/Logging/SemanticLoggingBenchmarks.cs b/src/benchmark/Akka.Benchmarks/Logging/SemanticLoggingBenchmarks.cs
new file mode 100644
index 00000000000..614cd1b82be
--- /dev/null
+++ b/src/benchmark/Akka.Benchmarks/Logging/SemanticLoggingBenchmarks.cs
@@ -0,0 +1,534 @@
+//-----------------------------------------------------------------------
+//
+// Copyright (C) 2009-2025 Lightbend Inc.
+// Copyright (C) 2013-2025 .NET Foundation
+//
+//-----------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Akka.Benchmarks.Configurations;
+using Akka.Event;
+using BenchmarkDotNet.Attributes;
+using static Akka.Benchmarks.Configurations.BenchmarkCategories;
+
+namespace Akka.Benchmarks.Logging
+{
+ ///
+ /// Benchmarks for semantic logging implementation in Akka.NET.
+ /// Tests template parsing, property extraction, and message formatting performance.
+ ///
+ /// Performance Targets:
+ /// - Template cache hit: <100ns
+ /// - Template parse (uncached): <5μs
+ /// - Full format operation: <2μs
+ /// - Property extraction: <1μs (with caching)
+ /// - GC pressure: <200 bytes per log call
+ ///
+ [Config(typeof(MicroBenchmarkConfig))]
+ [MemoryDiagnoser]
+ public class SemanticLoggingBenchmarks
+ {
+ // ============================================================================
+ // CATEGORY 1: Template Parsing - Cache Performance
+ // ============================================================================
+
+ private const string SimpleTemplate = "User {UserId} logged in";
+ private const string ComplexTemplate = "Request {RequestId} from {IpAddress} at {Timestamp:yyyy-MM-dd} returned {StatusCode} in {Duration:N2}ms";
+ private const string PositionalTemplate = "Value {0} and {1} and {2}";
+
+ private string[] _varyingTemplates;
+ private const int TemplateVariations = 100;
+
+ [GlobalSetup]
+ public void Setup()
+ {
+ // Pre-generate varying templates to test cache effectiveness
+ _varyingTemplates = new string[TemplateVariations];
+ for (int i = 0; i < TemplateVariations; i++)
+ {
+ _varyingTemplates[i] = $"User {{UserId}} performed action {{Action{i}}}";
+ }
+
+ // Warm up the cache with first template
+ MessageTemplateParser.GetPropertyNames(SimpleTemplate);
+ }
+
+ [Benchmark(Description = "Template parse - COLD (first time)")]
+ [BenchmarkCategory(MicroBenchmark, AkkaEventBenchmark)]
+ public IReadOnlyList TemplateParse_Cold()
+ {
+ // This simulates a cold cache by using a unique template each time
+ // Note: In reality this will pollute the cache, but shows worst-case
+ var template = $"Unique template {{Prop{Guid.NewGuid()}}}";
+ return MessageTemplateParser.GetPropertyNames(template);
+ }
+
+ [Benchmark(Description = "Template parse - WARM (cached)", Baseline = true)]
+ [BenchmarkCategory(MicroBenchmark, AkkaEventBenchmark)]
+ public IReadOnlyList TemplateParse_Warm()
+ {
+ // Should hit ThreadStatic cache - target <100ns
+ return MessageTemplateParser.GetPropertyNames(SimpleTemplate);
+ }
+
+ [Benchmark(Description = "Template parse - Complex template (cached)")]
+ [BenchmarkCategory(MicroBenchmark, AkkaEventBenchmark)]
+ public IReadOnlyList TemplateParse_ComplexCached()
+ {
+ return MessageTemplateParser.GetPropertyNames(ComplexTemplate);
+ }
+
+ [Benchmark(Description = "Template parse - Positional {0} (cached)")]
+ [BenchmarkCategory(MicroBenchmark, AkkaEventBenchmark)]
+ public IReadOnlyList TemplateParse_PositionalCached()
+ {
+ return MessageTemplateParser.GetPropertyNames(PositionalTemplate);
+ }
+
+ [Benchmark(Description = "Template parse - Cache thrashing (100 templates)")]
+ [BenchmarkCategory(MicroBenchmark, AkkaEventBenchmark)]
+ public IReadOnlyList TemplateParse_CacheThrashing()
+ {
+ // Tests LRU eviction by cycling through many templates
+ var result = default(IReadOnlyList);
+ for (int i = 0; i < TemplateVariations; i++)
+ {
+ result = MessageTemplateParser.GetPropertyNames(_varyingTemplates[i]);
+ }
+ return result;
+ }
+
+ // ============================================================================
+ // CATEGORY 2: Property Extraction - LogMessage Performance
+ // ============================================================================
+
+ private LogMessage _simpleLogMessage1Param;
+ private LogMessage _simpleLogMessage3Params;
+ private LogMessage _complexLogMessage5Params;
+ private LogMessage _positionalLogMessage;
+
+ [GlobalSetup(Target = nameof(PropertyExtraction_1Param) + "," +
+ nameof(PropertyExtraction_3Params) + "," +
+ nameof(PropertyExtraction_5Params) + "," +
+ nameof(PropertyExtraction_Positional) + "," +
+ nameof(PropertyExtraction_Cached) + "," +
+ nameof(GetProperties_1Param) + "," +
+ nameof(GetProperties_3Params) + "," +
+ nameof(GetProperties_5Params) + "," +
+ nameof(GetProperties_Cached))]
+ public void SetupPropertyExtraction()
+ {
+ _simpleLogMessage1Param = new LogMessage>(
+ DefaultLogMessageFormatter.Instance,
+ "User {UserId} logged in",
+ new LogValues(12345)
+ );
+
+ _simpleLogMessage3Params = new LogMessage>(
+ DefaultLogMessageFormatter.Instance,
+ "User {UserId} from {IpAddress} at {Timestamp}",
+ new LogValues(12345, "192.168.1.1", DateTime.UtcNow)
+ );
+
+ _complexLogMessage5Params = new LogMessage>(
+ DefaultLogMessageFormatter.Instance,
+ ComplexTemplate,
+ new LogValues(
+ Guid.NewGuid(), "192.168.1.1", DateTime.UtcNow, 200, 123.45
+ )
+ );
+
+ _positionalLogMessage = new LogMessage>(
+ DefaultLogMessageFormatter.Instance,
+ "Value {0} and {1} and {2}",
+ new LogValues(42, "test", 3.14)
+ );
+ }
+
+ [Benchmark(Description = "PropertyNames - 1 param (lazy init)")]
+ [BenchmarkCategory(MicroBenchmark, AkkaEventBenchmark)]
+ public IReadOnlyList PropertyExtraction_1Param()
+ {
+ // Tests lazy initialization cost
+ var msg = new LogMessage>(
+ DefaultLogMessageFormatter.Instance,
+ SimpleTemplate,
+ new LogValues(12345)
+ );
+ return msg.PropertyNames;
+ }
+
+ [Benchmark(Description = "PropertyNames - 3 params (lazy init)")]
+ [BenchmarkCategory(MicroBenchmark, AkkaEventBenchmark)]
+ public IReadOnlyList PropertyExtraction_3Params()
+ {
+ var msg = new LogMessage>(
+ DefaultLogMessageFormatter.Instance,
+ "User {UserId} from {IpAddress} at {Timestamp}",
+ new LogValues(12345, "192.168.1.1", DateTime.UtcNow)
+ );
+ return msg.PropertyNames;
+ }
+
+ [Benchmark(Description = "PropertyNames - 5 params (lazy init)")]
+ [BenchmarkCategory(MicroBenchmark, AkkaEventBenchmark)]
+ public IReadOnlyList PropertyExtraction_5Params()
+ {
+ var msg = new LogMessage>(
+ DefaultLogMessageFormatter.Instance,
+ ComplexTemplate,
+ new LogValues(
+ Guid.NewGuid(), "192.168.1.1", DateTime.UtcNow, 200, 123.45
+ )
+ );
+ return msg.PropertyNames;
+ }
+
+ [Benchmark(Description = "PropertyNames - Positional")]
+ [BenchmarkCategory(MicroBenchmark, AkkaEventBenchmark)]
+ public IReadOnlyList PropertyExtraction_Positional()
+ {
+ var msg = new LogMessage>(
+ DefaultLogMessageFormatter.Instance,
+ PositionalTemplate,
+ new LogValues(42, "test", 3.14)
+ );
+ return msg.PropertyNames;
+ }
+
+ [Benchmark(Description = "PropertyNames - Cached (2nd access)")]
+ [BenchmarkCategory(MicroBenchmark, AkkaEventBenchmark)]
+ public IReadOnlyList PropertyExtraction_Cached()
+ {
+ // Should be cached after first access - target ~10ns
+ return _simpleLogMessage1Param.PropertyNames;
+ }
+
+ // ============================================================================
+ // CATEGORY 3: GetProperties() - Dictionary Construction
+ // ============================================================================
+
+ [Benchmark(Description = "GetProperties - 1 param")]
+ [BenchmarkCategory(MicroBenchmark, AkkaEventBenchmark)]
+ public IReadOnlyDictionary GetProperties_1Param()
+ {
+ return _simpleLogMessage1Param.GetProperties();
+ }
+
+ [Benchmark(Description = "GetProperties - 3 params")]
+ [BenchmarkCategory(MicroBenchmark, AkkaEventBenchmark)]
+ public IReadOnlyDictionary GetProperties_3Params()
+ {
+ return _simpleLogMessage3Params.GetProperties();
+ }
+
+ [Benchmark(Description = "GetProperties - 5 params")]
+ [BenchmarkCategory(MicroBenchmark, AkkaEventBenchmark)]
+ public IReadOnlyDictionary GetProperties_5Params()
+ {
+ return _complexLogMessage5Params.GetProperties();
+ }
+
+ [Benchmark(Description = "GetProperties - Cached (2nd access)")]
+ [BenchmarkCategory(MicroBenchmark, AkkaEventBenchmark)]
+ public IReadOnlyDictionary GetProperties_Cached()
+ {
+ // Should be cached - target ~5ns
+ return _simpleLogMessage1Param.GetProperties();
+ }
+
+ // ============================================================================
+ // CATEGORY 4: Message Formatting - SemanticLogMessageFormatter vs Default
+ // ============================================================================
+
+ private object[] _args1 = new object[] { 12345 };
+ private object[] _args3 = new object[] { 12345, "192.168.1.1", DateTime.UtcNow };
+ private object[] _args5 = new object[] { Guid.NewGuid(), "192.168.1.1", DateTime.UtcNow, 200, 123.45 };
+
+ [Benchmark(Description = "Format - Semantic 1 param")]
+ [BenchmarkCategory(MicroBenchmark, AkkaEventBenchmark)]
+ public string Format_Semantic_1Param()
+ {
+ return SemanticLogMessageFormatter.Instance.Format(SimpleTemplate, _args1);
+ }
+
+ [Benchmark(Description = "Format - Semantic 3 params")]
+ [BenchmarkCategory(MicroBenchmark, AkkaEventBenchmark)]
+ public string Format_Semantic_3Params()
+ {
+ return SemanticLogMessageFormatter.Instance.Format(
+ "User {UserId} from {IpAddress} at {Timestamp}",
+ _args3
+ );
+ }
+
+ [Benchmark(Description = "Format - Semantic 5 params")]
+ [BenchmarkCategory(MicroBenchmark, AkkaEventBenchmark)]
+ public string Format_Semantic_5Params()
+ {
+ return SemanticLogMessageFormatter.Instance.Format(ComplexTemplate, _args5);
+ }
+
+ [Benchmark(Description = "Format - Semantic with format spec {Value:N2}")]
+ [BenchmarkCategory(MicroBenchmark, AkkaEventBenchmark)]
+ public string Format_Semantic_WithFormatSpec()
+ {
+ return SemanticLogMessageFormatter.Instance.Format(
+ "Duration was {Duration:N2}ms",
+ new object[] { 123.456789 }
+ );
+ }
+
+ [Benchmark(Description = "Format - Default (positional) 3 params")]
+ [BenchmarkCategory(MicroBenchmark, AkkaEventBenchmark)]
+ public string Format_Default_3Params()
+ {
+ return DefaultLogMessageFormatter.Instance.Format(
+ "Value {0} and {1} and {2}",
+ _args3
+ );
+ }
+
+ [Benchmark(Description = "Format - Semantic Positional 3 params")]
+ [BenchmarkCategory(MicroBenchmark, AkkaEventBenchmark)]
+ public string Format_Semantic_Positional_3Params()
+ {
+ return SemanticLogMessageFormatter.Instance.Format(PositionalTemplate, _args3);
+ }
+
+ // ============================================================================
+ // CATEGORY 5: End-to-End Logging Pipeline
+ // ============================================================================
+
+ private sealed class BenchmarkLogAdapter : LoggingAdapterBase
+ {
+ public LogEvent LastLog { get; private set; }
+ private readonly string _logSource;
+ private readonly Type _logClass;
+
+ public BenchmarkLogAdapter(ILogMessageFormatter formatter) : base(formatter)
+ {
+ _logSource = LogSource.Create(this).Source;
+ _logClass = typeof(BenchmarkLogAdapter);
+ }
+
+ public override bool IsDebugEnabled => true;
+ public override bool IsInfoEnabled => true;
+ public override bool IsWarningEnabled => true;
+ public override bool IsErrorEnabled => true;
+
+ protected override void NotifyLog(LogLevel logLevel, object message, Exception cause = null)
+ {
+ LastLog = new Info(cause, _logSource, _logClass, message);
+ }
+ }
+
+ private BenchmarkLogAdapter _defaultLogger;
+ private BenchmarkLogAdapter _semanticLogger;
+
+ [GlobalSetup(Target = nameof(EndToEnd_Default_NoParams) + "," +
+ nameof(EndToEnd_Default_1Param) + "," +
+ nameof(EndToEnd_Default_3Params) + "," +
+ nameof(EndToEnd_Semantic_NoParams) + "," +
+ nameof(EndToEnd_Semantic_1Param) + "," +
+ nameof(EndToEnd_Semantic_3Params) + "," +
+ nameof(EndToEnd_Semantic_WithProperties))]
+ public void SetupEndToEnd()
+ {
+ _defaultLogger = new BenchmarkLogAdapter(DefaultLogMessageFormatter.Instance);
+ _semanticLogger = new BenchmarkLogAdapter(SemanticLogMessageFormatter.Instance);
+ }
+
+ [Benchmark(Description = "E2E - Default formatter, no params")]
+ [BenchmarkCategory(MicroBenchmark, AkkaEventBenchmark)]
+ public LogEvent EndToEnd_Default_NoParams()
+ {
+ _defaultLogger.Info("User logged in");
+ return _defaultLogger.LastLog;
+ }
+
+ [Benchmark(Description = "E2E - Default formatter, 1 param")]
+ [BenchmarkCategory(MicroBenchmark, AkkaEventBenchmark)]
+ public LogEvent EndToEnd_Default_1Param()
+ {
+ _defaultLogger.Info("User {0} logged in", 12345);
+ return _defaultLogger.LastLog;
+ }
+
+ [Benchmark(Description = "E2E - Default formatter, 3 params")]
+ [BenchmarkCategory(MicroBenchmark, AkkaEventBenchmark)]
+ public LogEvent EndToEnd_Default_3Params()
+ {
+ _defaultLogger.Info("User {0} from {1} at {2}", 12345, "192.168.1.1", DateTime.UtcNow);
+ return _defaultLogger.LastLog;
+ }
+
+ [Benchmark(Description = "E2E - Semantic formatter, no params")]
+ [BenchmarkCategory(MicroBenchmark, AkkaEventBenchmark)]
+ public LogEvent EndToEnd_Semantic_NoParams()
+ {
+ _semanticLogger.Info("User logged in");
+ return _semanticLogger.LastLog;
+ }
+
+ [Benchmark(Description = "E2E - Semantic formatter, 1 param")]
+ [BenchmarkCategory(MicroBenchmark, AkkaEventBenchmark)]
+ public LogEvent EndToEnd_Semantic_1Param()
+ {
+ _semanticLogger.Info("User {UserId} logged in", 12345);
+ return _semanticLogger.LastLog;
+ }
+
+ [Benchmark(Description = "E2E - Semantic formatter, 3 params")]
+ [BenchmarkCategory(MicroBenchmark, AkkaEventBenchmark)]
+ public LogEvent EndToEnd_Semantic_3Params()
+ {
+ _semanticLogger.Info("User {UserId} from {IpAddress} at {Timestamp}",
+ 12345, "192.168.1.1", DateTime.UtcNow);
+ return _semanticLogger.LastLog;
+ }
+
+ [Benchmark(Description = "E2E - Semantic with GetProperties()")]
+ [BenchmarkCategory(MicroBenchmark, AkkaEventBenchmark)]
+ public IReadOnlyDictionary EndToEnd_Semantic_WithProperties()
+ {
+ _semanticLogger.Info("User {UserId} from {IpAddress}", 12345, "192.168.1.1");
+ var logEvent = _semanticLogger.LastLog;
+ if (logEvent.TryGetProperties(out var props))
+ return props;
+ return null;
+ }
+
+ // ============================================================================
+ // CATEGORY 6: Allocation Benchmarks - Memory Pressure Analysis
+ // ============================================================================
+
+ [Benchmark(Description = "Allocations - Parse template (cold)")]
+ [BenchmarkCategory(MicroBenchmark, AkkaEventBenchmark)]
+ public IReadOnlyList Allocations_ParseCold()
+ {
+ // Unique template to avoid cache
+ var template = $"Event {{Id}} at {{Time}} with {{Data}}";
+ return MessageTemplateParser.GetPropertyNames(template);
+ }
+
+ [Benchmark(Description = "Allocations - Format semantic 3 params")]
+ [BenchmarkCategory(MicroBenchmark, AkkaEventBenchmark)]
+ public string Allocations_FormatSemantic()
+ {
+ return SemanticLogMessageFormatter.Instance.Format(
+ "User {UserId} from {IpAddress} at {Timestamp}",
+ new object[] { 12345, "192.168.1.1", DateTime.UtcNow }
+ );
+ }
+
+ [Benchmark(Description = "Allocations - GetProperties 3 params")]
+ [BenchmarkCategory(MicroBenchmark, AkkaEventBenchmark)]
+ public IReadOnlyDictionary Allocations_GetProperties()
+ {
+ var msg = new LogMessage>(
+ DefaultLogMessageFormatter.Instance,
+ "User {UserId} from {IpAddress} at {Timestamp}",
+ new LogValues(12345, "192.168.1.1", DateTime.UtcNow)
+ );
+ return msg.GetProperties();
+ }
+
+ [Benchmark(Description = "Allocations - Full log + properties")]
+ [BenchmarkCategory(MicroBenchmark, AkkaEventBenchmark)]
+ public IReadOnlyDictionary Allocations_FullPipeline()
+ {
+ _semanticLogger.Info("User {UserId} from {IpAddress} performed {Action}",
+ 12345, "192.168.1.1", "login");
+ if (_semanticLogger.LastLog.TryGetProperties(out var props))
+ return props;
+ return null;
+ }
+
+ // ============================================================================
+ // CATEGORY 7: Escaped Brace Handling
+ // ============================================================================
+
+ private const string EscapedBracesOnly = "Use {{ and }} for literals";
+ private const string EscapedBracesWithPlaceholder = "{First}}} text {{more {Second}";
+ private const string NestedEscapedBraces = "{{{UserId}}}";
+ private const string TrailingEscapedBrace = "{UserId}}";
+
+ [Benchmark(Description = "Format - Escaped braces only (no placeholders)")]
+ [BenchmarkCategory(MicroBenchmark, AkkaEventBenchmark)]
+ public string Format_EscapedBracesOnly()
+ {
+ // Tests UnescapeBraces path - no placeholders, just {{ and }}
+ return SemanticLogMessageFormatter.Instance.Format(EscapedBracesOnly, Array.Empty