Production Checklist

Read time 4 minutes
On this page

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:

  • NetworkSocketOptions
  • DispatchOptions
  • ConnectionQuotaOptions
  • ConnectionGuardOptions
  • ConnectionBanStoreOptions
  • ConnectionBlacklistStoreOptions
  • TrustedProxyOptions
  • ConnectionHubOptions
  • TimingWheelOptions
  • NetworkCallbackOptions

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:

  • ILogger
  • PacketRegistry.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:

  • TcpListenerBase or UdpListenerBase starts cleanly
  • GenerateReport() works
  • listen port and backlog are correct
  • TimingWheelOptions.IdleTimeoutMs (and connection.ExcludeFromIdleTimeout for 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 direct PacketContext<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:

  • ConnectionGuard
  • TokenBucketLimiter
  • PolicyRateLimiter

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:

  • Capacity should reflect peak usage
  • Preallocate should 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:

  • MaxPendingNormalCallbacks
  • MaxPendingPerIp
  • CallbackWarningThreshold

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.