Production Checklist
On this page
- 1. Startup and configuration#
- 2. Shared services#
- 3. Listener safety#
- 4. Dispatch safety#
- 5. Handler design#
- 6. Throttling and abuse controls#
- 7. Middleware review#
- 8. Pooling and memory pressure#
- 9. Callback flood protection#
- 10. Observability#
- 11. UDP-specific checks#
- 12. Failure drills#
- 13. Release checklist#
- Example validation pass#
- Worked example#
- Recommended Next Pages#
Use this checklist before calling your Nalix server production ready.
The goal is not perfection. The goal is to catch the failure points that show up once real traffic starts.
Treat it as a release gate, not as a general introduction to the framework.
1. Startup and configuration
Start by confirming that the important option types are loaded and validated during startup:
NetworkSocketOptionsDispatchOptionsConnectionQuotaOptionsConnectionGuardOptionsConnectionBanStoreOptionsConnectionBlacklistStoreOptionsTrustedProxyOptionsConnectionHubOptionsTimingWheelOptionsNetworkCallbackOptions
If you skip validation on types that expose it, bad values usually fail later and less clearly.
2. Shared services
Make sure these are registered once and early:
ILoggerPacketRegistry.Build()called after assembly load- any app services your handlers depend on
Also make sure the server and client agree on packet catalog and registry assumptions.
3. Listener safety
Before launch, check:
TcpListenerBaseorUdpListenerBasestarts cleanlyGenerateReport()works- listen port and backlog are correct
TimingWheelOptions.IdleTimeoutMs(andconnection.ExcludeFromIdleTimeoutfor exempt connections) match your real idle behavior expectations
4. Dispatch safety
Confirm the dispatcher:
- registers all required handlers
- registers middleware in the intended order
- uses explicit error handling
- can produce
GenerateReport()
If the dispatcher is central to your app, treat missing error handling as a release blocker.
5. Handler design
Review handlers for:
- correct
[PacketOpcode] - correct packet type
- appropriate return type
- no hidden expensive blocking work
- explicit use of
IPacketContext<TPacket>in handlers, and directPacketContext<TPacket>discussion only where runtime internals truly matter
If a handler performs slow I/O or expensive computation, decide whether it needs:
- timeout metadata
- rate limiting
- concurrency limiting
6. Throttling and abuse controls
Before production, decide intentionally which of these you want to use:
ConnectionGuardTokenBucketLimiterPolicyRateLimiter
At minimum, most public-facing servers should have:
- connection admission control
- one packet-level rate/concurrency protection strategy
7. Middleware review
Confirm that middleware behavior is easy to predict:
- inbound middleware is ordered correctly
- outbound behavior is intentional
- short-circuit paths send useful control responses when appropriate
- custom metadata used by middleware is actually registered
Good production middleware should be:
- small
- deterministic
- observable in logs
8. Pooling and memory pressure
Network-layer pool capacities (accept contexts, socket args, receive contexts, timeout tasks, transports, connections) are derived internally from ConnectionGuardOptions.MaxConnections. Set that option to your expected peak concurrent connection count.
A useful rule of thumb:
Capacityshould reflect peak usagePreallocateshould reflect steady-state warm usage
Too low:
- more allocations
- more GC pressure
Too high:
- unnecessary memory footprint
9. Callback flood protection
If you expect internet-facing traffic, review:
MaxPendingNormalCallbacksMaxPendingPerIpCallbackWarningThreshold
These values protect the callback path from flood behavior and are worth setting intentionally.
10. Observability
Before production, make sure you can obtain:
- listener reports
- protocol reports
- connection hub reports
- dispatch reports
- limiter/gate reports where relevant
If debugging requires attaching a debugger just to learn basic system state, observability is still too weak.
11. UDP-specific checks
If UDP is enabled, confirm:
- session identity is established before UDP traffic is trusted
- connection secret is initialized
- replay window assumptions are acceptable
IsAuthenticated(...)performs the right checks- unauthenticated and unknown-session drops are visible in diagnostics
12. Failure drills
Run at least these drills once:
- wrong packet opcode
- malformed packet
- idle timeout
- limiter rejection
- dispatcher with missing handler
- UDP auth failure
You do not need a perfect chaos environment. You do need proof that the failure mode is understandable.
13. Release checklist
Final short checklist:
- all startup options validate
- reports generate without crashing
- handlers and middleware are registered intentionally
- abuse controls are enabled
- logging is usable under failure
- timeout values are deliberate
- server boot and shutdown paths are tested
Example validation pass
Use one short smoke test before release:
NetworkSocketOptions socket = ConfigurationManager.Instance.Get<NetworkSocketOptions>();
socket.Validate();
listener.Activate(ct);
Console.WriteLine(listener.GenerateReport());
Console.WriteLine(dispatch.GenerateReport());
Console.WriteLine(connectionHub.GenerateReport());
listener.Deactivate(ct);
Activate(...) and Deactivate(...) on listeners are synchronous lifecycle calls. The async host-facing entrypoint is NetworkApplication.RunAsync(...).
Worked example
Putting the checklist into practice: a handler with explicit contracts, rate limiting, and error handling wired through the hosting builder.
// samples/HelloWorld/HelloWorld.Contracts/HelloRequestPacket.cs
[GenerateFormatter]
[SerializePackable(SerializeLayout.Explicit)]
public sealed partial class HelloRequestPacket
: PacketBase<HelloRequestPacket>, IFixedSizeSerializable, IPacketStaticOpcode
{
public static ushort StaticOpCode => 0x7001;
[SerializeOrder(0)]
public byte Greeting { get; set; }
}
// samples/HelloWorld/HelloWorld.Server/Program.cs, extended with production concerns
await using NetworkApplication app = NetworkApplication.CreateBuilder()
.UseLogger(logger)
.MapHandlers(typeof(HelloHandlers))
.ConfigureDispatchOptions(options =>
{
options
.WithMiddleware(new RateLimitMiddleware())
.WithErrorHandling((ex, opcode) =>
logger.Error($"Unhandled error in opcode 0x{opcode:X4}", ex));
})
.MapTcp<DefaultProtocol>().OnPort(57206).Bind()
.Build();
await app.RunAsync(cts.Token);
Full source: samples/HelloWorld/HelloWorld.Contracts/HelloRequestPacket.cs, samples/HelloWorld/HelloWorld.Server/Program.cs
On the client, set an explicit timeout on every request instead of relying on defaults:
// samples/HelloWorld/HelloWorld.Client/Program.cs
HelloResponsePacket response = await session.RequestAsync<HelloResponsePacket>(
request,
RequestOptions.Default.WithTimeout(5_000));
Full source: samples/HelloWorld/HelloWorld.Client/Program.cs
Resource cleanup for buffer leases is handled automatically when you use IPacketContext<T> in your handler — you don’t dispose anything yourself.