Configuring for Production

Running a Nuts node in a production environment has additional requirements regarding security and data integrity compared to development or test environments. This page instructs how to configure your node for running in a production environment and what to consider.


Make sure your storage configuration is set up to backup your node’s data and private keys.

HTTP Interface Configuration

By default all HTTP endpoints get bound to the default HTTP interface on :1323. You can configure endpoints to have alternative configuration (e.g. CORS, TLS, authentication) and/or be bound on an alternative port using the alt property. All configuration properties that apply to the http.default HTTP interface are applicable to the alternative interface you configure as well (see the example below).

For production there are a few points to consider; enabling CORS, using alternative HTTP interfaces, and authentication.

Binding HTTP interfaces

Some HTTP endpoints are required to be accessible by the public and others only meant for administrator or your own XIS. You can determine the intended public by observing the first part of the URL:

  • Endpoints that start with /public should be accessible by the general public,

  • /internal is meant for XIS application integration and administrators.

It’s advisable to make sure internal endpoints aren’t reachable from public networks. You can achieve this by binding it to a different port, by using a reverse proxy, or both. Use the address property of a HTTP binding to expose it on a different port:

  # The following is the default binding which endpoints are bound to,
  # which don't have an alternative bind specified under ``alt``. Since it's a default it can be left out or
  # be used to override the default bind address.
  # If you specify an alt bind without address, it binds to the default HTTP interface, but
  # the configuration of the default bind will NOT apply to the alt bind. If required, you need to repeat the config (e.g. cors/auth) for the alt bind.
    address: :1323
    # The following binds all endpoints starting with ``/internal`` to ``internal.lan:1111``
      address: internal.lan:1111
        type: token_v2
        authorizedkeyspath: /opt/nuts/authorized_keys
    # The following binds all endpoints starting with ``/public`` to ``:443`` and enables TLS for it
      address: :443
      # Given we bind on port 443, we probably want to enable TLS for this endpoint
      tls: server
      # The following enables cross-domain requests (CORS) from

Cross Origin Resource Sharing (CORS)

In some deployments CORS can be required for the public IRMA authentication endpoints when the user-facing authentication page is hosted on a (sub)domain that differs from Nuts Node’s IRMA backend. CORS can be enabled on a specific HTTP interface by specifying the domains allowed to make CORS requests as cors.origin (see the example above). Although you can enable CORS on the default endpoint it’s not advised to do so in a production environment, because CORS itself opens up new attack vectors on node administrators.


You should secure some HTTP endpoints (notably /internal) with authentication. The Nuts node supports JWT Bearer Token authentication which can be enabled on any HTTP endpoint. You can enable it by setting auth.type (see example above) to token_v2 for the specific bind. The specified path, and all its subpaths, will then require a JWT token generated by an external process and signed with a trusted private/public keypair.

The optional auth.audience parameter sets the expected audience value for JWT tokens. The default value for this parameter is determined by the hostname configured on the nuts node, and thus administrators may wish to explicitly set this parameter when using reverse proxies or under other circumstances where the known DNS name of the nuts node differs from its hostname.

Users are authorized access to APIs by the administrator of the nuts node. The administrator must configure the public key of API callers in an authorized_keys file as specified in the auth.type.authorizedkeyspath setting. This file should contain one or more trusted keys, in the standard SSH format. ECDSA, Ed25519, and RSA (>=2048-bit) keys are accepted. Each line in the file must contain the key-type, key-specification, and user name that is authorized, for example ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH1VNKtThJiI6c5zjLn/6EjRq1PtfM4qw4HM71zivIVn Note that this file should be a distinct authorized_keys file from that used to grant console access to the nuts node. API access and SSH access are two entirely different matters and we are simply using this well known configuration file format. The nuts node does not integrate in any way with the SSH subsystem on the host OS.

The Nuts foundation provides a utility for generating JWT tokens on our GitHub. Unlike the legacy authentication method this tool does not need to be executed on the nuts node itself.

Alternatively, developers and integrators may choose to implement their own JWT generation process which must conform to the requirements described in Custom JWT Generation.

Generating Keys With OpenSSL

To generate an Ed25519 key with OpenSSL:

openssl genpkey -algorithm ed25519 -out /path/to/keyfile.pem

To generate an ECDSA key with OpenSSL:

openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-521 -pkeyopt ec_param_enc:named_curve -out /path/to/keyfile.pem

To generate an RSA key with OpenSSL:

openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:4096 -out /path/to/rsa-private.pem

Generating Keys With SSH

To generate an Ed25519 key with using ssh-keygen:

ssh-keygen -t ed25519 -f /path/to/keyfile

To generate an ECDSA key using ssh-keygen:

ssh-keygen -t ecdsa -b 521 -f /path/to/keyfile

To generate an RSA key using ssh-keygen:

ssh-keygen -t rsa -b 4096 -f /path/to/keyfile

authorized_keys files

The keys permitted to issue and sign JWT bearer tokens are specified in the authorized_keys format.

authorized_keys files are made up of multiple lines, each line specifying one more key/user that is authorized. For more information on the authorized_keys format see the AUTHORIZED_KEYS FILE FORMAT section of the man page.

The following is an example authorized_keys file. Each line specifies the key type, the public key, and the username:

ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBAPwLGkaO5dWEx29sW4xnmv/s8+Nzj3mnkY6SX9Qnb91oyPayZV8Ts3TXSMKlkyYHVcIz/nAxRgxgKBTMwZc2wE=
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAwaOa7iN1gnKEfiZAA7lhu3SIvfdzYE3VbswsVUQP7F
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH1VNKtThJiI6c5zjLn/6EjRq1PtfM4qw4HM71zivIVn
ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBC54Az33UVYdRSTb/2N9LiZtL7TRiEox5+rJcnMYz+t30l4UG5Y8ZN6L2dJCCFWyQeeJ/oTOY915L9/miklDyhk=

Adding PEM files to authorized_keys

To generate the authorized_keys format from an RSA or ECDSA PEM file use the following command:

ssh-keygen -y -f /path/to/private.pem

For ECDSA keys you will see something like the following:

ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBAPwLGkaO5dWEx29sW4xnmv/s8+Nzj3mnkY6SX9Qnb91oyPayZV8Ts3TXSMKlkyYHVcIz/nAxRgxgKBTMwZc2wE=

For RSA keys you will see something like the following:

ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCySRpyEQwIQmf6vd+uJk54aiS7VDhbtTIMNfTRLbY1eZ+FeFsf1mbDMqsMYjwp0oBlcCL2sUrBBcsJLnHLOiT3rpziknv0kHHSTuv2sbIuHKGsVtNBtWcMMp6q56VRqfPkwa4okSde1o8hCaBUG5S3a3LA8fPWOWzpbvboY/rWeWmCNhAeaL3Koo01W5G1kC6K1mBe2ZPye6F/Q9geGTCx4GBDwDP0jp5qkpu8kf3PxlP452VZGH+pXvCffXzb/MFGBvP4N29eUANWpZed8PnJqcTlxLTTHZSkZlUn/T3U9us/Uf38nHhtZsnz3YKM+tJnBHcono8QItgKMz+tK+p5I7IBD+oyYdQ2qFJBCCUJWFnxaJfxRUG//ar1OjorVtoqHTh8mM/Q0yXEcHAfYzjHOgeyUUk34oxy4ormvmLOOx2KxEYFGJL+asbN0SaV+OS6LTZ6sySNjumYYCDJ/Mi7PROF2q2C+YmybdN85+J2t15aMxLqebJWRJHrMrZ87cfDgSrW6bm7gJqmKPNiuROubYyBYusZCznaouZRyCZ9Fa1T4o2fYrESpUCqvQaypcN5K7d94vDjvvRhid1YGGgzmlqCRDi/+70GC+tMcmA/Zn/uSlegyK2XfF1YFhCQDKM1WtMEii2wkT8jqB46AbfsTSuIvySPL5F9uZFp7wzkxw==

To authorize these keys create a new text file or open an existing authorized_keys file and on a new line place the exported format above followed by a space and then the username or application name which is trusted to use the key (usually specified by email address):

ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBAPwLGkaO5dWEx29sW4xnmv/s8+Nzj3mnkY6SX9Qnb91oyPayZV8Ts3TXSMKlkyYHVcIz/nAxRgxgKBTMwZc2wE=

The above ssh-keygen command unfortunately fails for Ed25519 PEM keys at the time of this writing due to a bug and poor recent support for Ed25519 in libcrypto packages. The nuts-jwt-generator method below is recommended until this bug is fixed.

Adding JWK or PEM files to authorized_keys using nuts-jwt-generator

To convert RSA, ECDSA, and Ed25519 keys stored in JWK or PEM files to the authorized_keys format the nuts-jwt-generator tool may be used.

The following command generates the authorized_keys representation from a JWK or PEM key: nuts-jwt-generator -i /path/to/private.pem -export-authorized-key

This will generate output like the following:

ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAwaOa7iN1gnKEfiZAA7lhu3SIvfdzYE3VbswsVUQP7F

Legacy Authentication

This authentication method has been superceded and users should consider updating their deployments.

You might want to secure some HTTP endpoints (notably /internal) with authentication. The Nuts node supports JWT Bearer Token authentication which can be enabled on any HTTP endpoint. You can enable it by setting auth.type (see example above) to token for the specific bind. The specified path, and its all subpaths, will then require a JWT token generated by the Nuts node. You can generate a token using the following command, which needs to be executed on the Nuts node itself:

nuts http gen-token admin 365

This command generates a token for a user named “admin” which is valid for 365 days. The user’s name is used for logging HTTP requests. It outputs the token, which should be passed using --token or --token-file when performing CLI operations or as Authorization Bearer token header for other clients, such as XIS applications. You can also save it to a file named .nuts-client.cfg in your user’s home directory, which will be read by CLI when no other token flags are passed. See the server configuration and CLI command reference for more information.


To aid problem diagnosis every node in a network should share some information about itself; the type and version of software it’s running, which peers it is connected to and how long it’s been up. This helps others diagnosing issues when others experience communication problems with your, and other nodes. Although discouraged, this can be disabled by specifying 0 for network.v2.diagnosticsinterval.

Monitoring Harmful Activity

Deployment should monitor incoming traffic for potentially harmful activity, e.g. excessive amounts of valid requests, or requests that generate lots of errors. This could be caused by a malicious actor (trying to cause denial of service or trying to gain access to the system’s data), but it could also be caused by a bug in a client application (e.g. XIS/EHR) or a bug in the Nuts Node.

This is no different than monitoring access to any other valued system, so it can be solved using the existing tools to prevent (distributed) denial of service attacks and intrusion detection.

Special attention should be given to:

  • the /public HTTP endpoint (because it has no authentication and is used to access IRMA sessions),

  • the gRPC interface (because it allows other nodes to read/write network transactions),

  • the /n2n (because it allows other to request access tokens for your systems)

Since the gRPC interface and /n2n HTTP endpoint are authenticated using the TLS client certificate, you can monitor (and potentially deny) access to these endpoint by identifying the exact client.