Secure your node-opcua server: 20 rules for production

By Etienne Rossignon, CEO of Sterfive and maintainer of node-opcua. Version 1.4, updated 5 November 2026. Applies to node-opcua 2.187 and later (Node.js 22.13 or later).

Short answer: By default, a node-opcua server accepts anonymous users, shows endpoints without security, and trusts all unknown certificates. This is correct for development only. For production, use dedicated certificate managers that do not accept unknown certificates. Refuse anonymous users, use SignAndEncrypt only, and give roles to users. Run the server as a non-root user that is the only owner of the PKI folder.

About this guide

This guide tells you how to make a node-opcua OPC UA server safe for production. It is for developers, integrators and machine builders.

The text uses ASD-STE100 Simplified Technical English (STE), Issue 9. The sentences are short. Each instruction has one action. Each rule has the action and the reason. The names of settings, files and commands are technical names. We show them in code.

TL;DR: the 20 rules

Platform and operation

  1. Upgrade node-opcua each month.
  2. Do not run the server as root.
  3. Restrict the PKI folder to the server user.
  4. Keep the audit events.

Certificates and PKI

  1. Use a dedicated OPCUACertificateManager for the server.
  2. Set automaticallyAcceptUnknownCertificate: false.
  3. Encrypt the private key with privateKeyPassphrase.
  4. Advanced: keep the private key in a TPM or an HSM.
  5. Use CA certificates from a GDS, with a short validity and automatic renewal. Do not use self-signed certificates.

Communication, endpoints and authentication

  1. Use SignAndEncrypt only. Remove None and Basic128Rsa15.
  2. Set allowAnonymous: false.
  3. Use X.509 user certificates, not passwords.
  4. Limit the sessions and the connections.
  5. Listen only on the OT interface (host). Always set port.
  6. Show only the endpoint URLs that clients use. Put all of them in the certificate.
  7. Refuse unknown endpoint URLs (requestExactEndpointUrl).
  8. Set the security on each listener. Per-URL overrides are not a barrier.

Address space and authorization

  1. Give roles to users (OPC UA Part 18).
  2. Make variables read-only, unless a client must write them. Always set accessLevel.
  3. Give Call on methods to specified roles only, on encrypted channels.

Section 3 gives the details and the reasons, in four categories. Section 5 gives a complete example, and examples for NAT and for two networks.

1. Why this guide is necessary

The rules that follow apply to all OPC UA servers that use node-opcua. This includes the servers in Node-RED nodes and edge gateways. It also includes the servers that you write directly with the node-opcua SDK. The rules apply to node-opcua 2.187 and later (Node.js 22.13 or later).

The default settings of the node-opcua SDK make the first steps easy for a new user. Thus, the default security is relaxed. This is correct for development. Before you put the project into production, you must make the security stronger. Apply all the rules in this guide.

WARNING: The default node-opcua settings are for development. By default, the server accepts anonymous users. It also shows endpoints with the security mode None. The default certificate stores accept all unknown certificates automatically. A variable that you add without accessLevel is writable. Change these settings before you use the server in production.

2. The basic terms

Read these terms first. The rules use them.

TermWhat it is
EndpointAn address and a security configuration that the server shows to the clients. A client selects one endpoint to connect.
Security modeThe protection of the messages: None (no protection), Sign (integrity) or SignAndEncrypt (integrity and confidentiality).
Security policyThe set of algorithms for the signature and the encryption, for example Basic256Sha256.
Certificate managerThe store of certificates (PKI) of the application. It has the folders own, trusted, rejected and issuers. In node-opcua, it is an OPCUACertificateManager.
User identity tokenThe proof of the user identity in a session: anonymous, user name and password, or X.509 user certificate.
RoleA group of permissions, for example Observer, Operator or Engineer. The server gives roles to users.
Access levelThe attribute of a variable that tells if a client can read it (CurrentRead) or write it (CurrentWrite).
MethodA function on the server that a client can call. A method can change the process.

3. The 20 rules

Each rule has the action and the reason.

3.1 Platform and operation

These rules protect the host, the software and the records.

  1. Upgrade. Always use the latest node-opcua version. Examine your dependencies with npm outdated and npm audit each month. Reason: each release contains security corrections.
  2. Do not run the server as root. Make a dedicated system user for the server process. Run the process with this user only. Add the systemd or container limits of Recipes 10 and 11 of the Node-RED guide. Reason: an attacker who gets control of the process then gets only the rights of this user. The default OPC UA port 4840 is above 1024, so the server does not need root.
  3. Restrict the PKI folder. Give the PKI folder to the user that runs the server. Set the folder permissions to 700 and the file permissions to 600. No other user can then read or change it. Reason: the folder contains the private key and the list of trusted certificates. OPC 10000-4, clause 6.1.2, says that only the application can have access to the private key.
  4. Keep the audit events. Keep isAuditing: true (default). Send the audit events and the logs to your central log server. Reason: you know who did what, and when.

3.2 Certificates and PKI

These rules protect the identity of the server and the trust in the other applications.

  1. Use a dedicated certificate manager. Give an OPCUACertificateManager with its own rootFolder to serverCertificateManager. Do not share a store between two applications. Reason: the default store is in the configuration folder of the user, and other applications can use it too.
  2. Do not accept unknown certificates automatically. Set automaticallyAcceptUnknownCertificate: false. Put each trusted client certificate, or its CA, in the trusted folder. Reason: the default stores trust all clients. Then the certificate check does not protect the server.
  3. Protect the private key. Set privateKeyPassphrase to encrypt the key on the disk. Reason: a copy of the key file then does not give the identity of the server.
  4. Advanced: keep the private key in hardware. Set keyOperations to a TPM, HSM or KMS provider. See Using an HSM/KMS-held application key. The next edition of the NodeOPCUA book (not yet published) explains this procedure. Reason: the private key never goes into the Node.js process.
  5. Use CA certificates from a GDS, not self-signed certificates. Install the push certificate management on the server (see "How to enable the push certificate management" below). Then a Global Discovery Server (GDS) can issue the certificate, renew it and update the trust lists. Use a short validity, for example one year. Let the GDS renew the certificates before they expire. Reason: see "Why a GDS" below.

Why a GDS

By default, node-opcua makes a self-signed certificate that is valid for 10 years. This is correct for development. In production, self-signed certificates cause these problems:

  • Each client must trust each server certificate manually, and each server must trust each client certificate. With 50 servers and 20 clients, the operators must do up to 2,000 trust operations.
  • A certificate that is valid for 10 years gives an attacker 10 years to use a stolen private key.
  • There is no revocation. If a key is stolen, you must remove the certificate from all trust lists manually.
  • Persons forget the manual renewal. An expired certificate stops the communication, and thus the production.

A short validity is safe only with an automatic renewal. A GDS gives this automatic renewal:

Self-signed certificatesCA certificates from a GDS
TrustEach certificate on each peerOne CA certificate on each peer
Validity10 years (node-opcua default)Short, for example one year
RenewalManualAutomatic, before the expiry date
RevocationManual removal from all trust listsCRL that the GDS sends to all applications
Trust list updateManual, on each applicationSent by the GDS (OPC 10000-12)
Record of the operationsNoneCentral record in the GDS

See What is the OPC UA GDS?, GDS push and reverse connect and the Sterfive OPC UA GDS.

How to enable the push certificate management

  1. Install the package node-opcua-server-configuration.
  2. Initialize the server.
  3. Call installPushCertificateManagementOnServer. This adds the ServerConfiguration object of OPC 10000-12 to the address space.
  4. Give the SecurityAdmin role only to the identity of the GDS. Only this role can call the methods of ServerConfiguration.
  5. Start the server.
import { installPushCertificateManagementOnServer } from "node-opcua-server-configuration";

await server.initialize();
await installPushCertificateManagementOnServer(server);   // ServerConfiguration (OPC 10000-12)
await server.start();

Then the GDS can call CreateSigningRequest, UpdateCertificate and ApplyChanges, and write the trust list of the server. See the node-opcua-server-configuration package.

3.3 Communication, endpoints and authentication

These rules protect the network access, the connections and the sessions.

  1. Use secure connections only. Set securityModes to SignAndEncrypt. Remove None and Basic128Rsa15 from securityPolicies. Reason: with None, an attacker can read and change the messages. Basic128Rsa15 is an old and weak policy.
  2. Refuse anonymous users. Set allowAnonymous: false. Reason: the default value is true. Then all clients can open a session without an identity.
  3. Use X.509 user certificates, not passwords. Give a second dedicated OPCUACertificateManager to userCertificateManager. Set its automaticallyAcceptUnknownCertificate to false too. If you do not use passwords, give a userManager that refuses all passwords. Reason: a certificate is more difficult to steal or guess than a password.
  4. Limit the resources. Set serverCapabilities.maxSessions and maxConnectionsPerEndpoint to the values that your clients use. Reason: the limits prevent the exhaustion of the server.
  5. Listen only on the correct network interface. Always set port: the default of node-opcua is 26543, not 4840. If the host has two or more IP addresses, set host to the IP address of the OT network interface. By default, node-opcua listens on all interfaces (:: or 0.0.0.0). In node-opcua 2.187.22, the host of an entry in endpoints has no effect: all listeners use the top-level host. Reason: the server is then not reachable from the other networks, for example the office network.
  6. Show only the endpoint URLs that the clients use. Set hostname to the name that the clients use. Add other names or IP addresses with alternateHostname. Behind NAT, Docker published ports or a TCP proxy, add the external URL with advertisedEndpoints. The server still listens on port. Make sure that the server certificate contains all these names and IP addresses in its subjectAltName. node-opcua adds them automatically to a self-signed certificate. For a CA or GDS certificate, request all of them. Reason: the client compares the endpoint URL with the server certificate. A difference stops the connection, or teaches the operators to ignore certificate warnings.
  7. Refuse the unknown endpoint URLs. Set OPCUAServer.requestExactEndpointUrl = true, or the environment variable NODEOPCUA_SERVER_REQUEST_EXACT_ENDPOINT_URL=1. The server then refuses a CreateSession with an endpoint URL that it does not advertise (BadServiceUnsupported). With isAuditing: true, the server also raises an AuditUrlMismatchEventType event. Reason: by default, the server accepts an unknown URL and uses the first endpoint that agrees with the security settings.
  8. Set the security on each listener, and do not use per-URL overrides as a barrier. The entries in endpoints do not inherit the top-level security settings. Set securityModes, securityPolicies and allowAnonymous in each entry. All the URLs in advertisedEndpoints share one TCP listener. Thus, their securityModes, securityPolicies and allowAnonymous overrides change what the server shows, not what the listener accepts. If two networks must have different security, make two listeners with two ports. Reason: without explicit settings, an entry in endpoints uses the insecure defaults.

3.4 Address space and authorization

These rules control what each user can see, read, write and call.

  1. Give roles, not full access. Install the OPC UA role-based security (Part 18) with createRoleBasedSecurity from node-opcua-role-set-server. See the node-opcua-role-set-server README and the Role-Based Security and User Management guide. For a fail-closed server, set unresolvedPermissionPolicy: "deny". Reason: each user gets only the permissions that are necessary for the task.
  2. Make variables read-only, unless a client must write them. Always set accessLevel on each variable. If you do not set it, addVariable uses CurrentRead | CurrentWrite. Then all users can write the variable. Set accessLevel: "CurrentRead" on the variables that clients only read. Set CurrentWrite only on the variables that a client must change. On these variables, set rolePermissions that give Write to specified roles only. Do the same for the variables that the server keeps in persistent storage. Do not use userAccessLevel to control the users: it has the same value for all users. Reason: a writable variable can change the process or the stored configuration.
  3. Protect the methods. On each method, set rolePermissions that give Call to specified roles only. Set accessRestrictions to EncryptionRequired and SessionRequired. Reason: by default (unresolvedPermissionPolicy: "allow"), all users can call a method that has no rolePermissions. With these settings, the server accepts a call only from a user with a role. The call must also come in a session, on an encrypted channel.

RECOMMENDATION: For production projects, get a support contract with Sterfive. Sterfive maintains node-opcua. Support customers get early notice of security vulnerabilities, before the public disclosure. Then they can plan and apply the corrections before attackers know about the problem. See Sterfive support.

Also apply the host rules of the Node-RED guide (Recipes 10 to 12) to the OPC UA server. Open port 4840 only to known clients. If you do not use a discovery server, keep registerServerMethod at HIDDEN (default). Make sure that the applicationUri of the server agrees with its certificate.

4. Recipe: user and folder permissions on Linux

This example applies rules 2 and 3 on Linux:

# Rule 2: a dedicated system user without a login shell
sudo useradd --system --home /var/lib/my-opcua-server --create-home --shell /usr/sbin/nologin opcua

# Rule 3: only this user can read or change the PKI folders
sudo chown -R opcua:opcua /var/lib/my-opcua-server
sudo chmod 700 /var/lib/my-opcua-server /var/lib/my-opcua-server/pki /var/lib/my-opcua-server/user-pki
sudo find /var/lib/my-opcua-server -type d -exec chmod 700 {} +
sudo find /var/lib/my-opcua-server -type f -exec chmod 600 {} +

# Examine the result: the owner must be opcua, and the group and others must have no rights
ls -lR /var/lib/my-opcua-server | head -40

In the systemd unit of the server, set User=opcua, Group=opcua, UMask=0077, NoNewPrivileges=yes, ProtectSystem=strict and ReadWritePaths=/var/lib/my-opcua-server. With UMask=0077, the new files (for example the rejected certificates) also get the permission 600. If the server must listen on a port below 1024, add AmbientCapabilities=CAP_NET_BIND_SERVICE. Do not use root for this.

These rules apply common practices from other fields:

  • OPC 10000-4, clause 6.1.2: keep the private key of the application instance certificate in a location that only the application can access.
  • OWASP Docker Security Cheat Sheet: rule 2 (set a user), rule 3 (limit capabilities), rule 4 (prevent privilege escalation) and rule 8 (read-only file system).
  • Node.js Best Practices: run Node.js as a non-root user.
  • OWASP Node.js Security Cheat Sheet: apply the principle of least privilege, and the Node.js permission model.
  • NIST SP 800-82 Rev. 3 and IEC 62443: least privilege for OT systems.

5. Complete example

This example applies rules 4 to 7, 9, 10 to 14 and 18 to 20. It uses the packages node-opcua, node-opcua-data-model, node-opcua-types, node-opcua-role-set-common and node-opcua-server-configuration.

import {
    OPCUAServer,
    OPCUACertificateManager,
    MessageSecurityMode,
    SecurityPolicy,
    StatusCodes
} from "node-opcua";
import { AccessRestrictionsFlag } from "node-opcua-data-model";
import { PermissionType } from "node-opcua-types";
import { WellKnownRoleIds } from "node-opcua-role-set-common";
import { installPushCertificateManagementOnServer } from "node-opcua-server-configuration";

// Rules 5, 6 and 7: server PKI in its own folder, no automatic trust, encrypted private key
const serverCertificateManager = new OPCUACertificateManager({
    rootFolder: "/var/lib/my-opcua-server/pki",
    automaticallyAcceptUnknownCertificate: false,
    privateKeyPassphrase: async () => {
        const passphrase = process.env.OPCUA_KEY_PASSPHRASE;
        if (!passphrase) throw new Error("OPCUA_KEY_PASSPHRASE is not set");
        return passphrase;
    }
});

// Rule 12: user PKI for X.509 user identity tokens, in its own folder, no automatic trust
const userCertificateManager = new OPCUACertificateManager({
    rootFolder: "/var/lib/my-opcua-server/user-pki",
    automaticallyAcceptUnknownCertificate: false
});

const server = new OPCUAServer({
    host: "10.20.0.10",                                     // rule 14: OT interface only
    port: 4840,                                             // rule 14: the default is 26543
    serverCertificateManager,
    userCertificateManager,
    allowAnonymous: false,                                  // rule 11
    securityModes: [MessageSecurityMode.SignAndEncrypt],    // rule 10
    securityPolicies: [
        SecurityPolicy.Basic256Sha256,
        SecurityPolicy.Aes128_Sha256_RsaOaep,
        SecurityPolicy.Aes256_Sha256_RsaPss
    ],
    userManager: {
        isValidUser: () => false   // rule 12: no passwords, X.509 user certificates only
    },
    serverCapabilities: { maxSessions: 20 },                // rule 13
    maxConnectionsPerEndpoint: 20,
    isAuditing: true                                        // rule 4
});

await server.initialize();

// Rule 18: install role-based security here with createRoleBasedSecurity (node-opcua-role-set-server).
// Without roles, the rolePermissions below give nothing to the users.

// Rule 9: let a GDS manage the certificate (node-opcua-server-configuration).
await installPushCertificateManagementOnServer(server);

const addressSpace = server.engine.addressSpace;
const namespace = addressSpace.getOwnNamespace();
const machine = namespace.addObject({
    browseName: "Machine",
    organizedBy: addressSpace.rootFolder.objects
});

const readOnly = PermissionType.Browse | PermissionType.Read;

// Rule 19: a measured value is read-only.
// Always set accessLevel: the default of addVariable is "CurrentRead | CurrentWrite".
namespace.addVariable({
    componentOf: machine,
    browseName: "Temperature",
    dataType: "Double",
    accessLevel: "CurrentRead",
    rolePermissions: [
        { roleId: WellKnownRoleIds.Observer, permissions: readOnly },
        { roleId: WellKnownRoleIds.Operator, permissions: readOnly }
    ]
});

// Rule 19: a setpoint is writable, but only the Operator role has the Write permission.
// Do not set userAccessLevel: it is the same for all users. The roles decide.
namespace.addVariable({
    componentOf: machine,
    browseName: "TemperatureSetpoint",
    dataType: "Double",
    accessLevel: "CurrentRead | CurrentWrite",
    rolePermissions: [
        { roleId: WellKnownRoleIds.Observer, permissions: readOnly },
        { roleId: WellKnownRoleIds.Operator, permissions: readOnly | PermissionType.Write }
    ],
    accessRestrictions: AccessRestrictionsFlag.EncryptionRequired | AccessRestrictionsFlag.SessionRequired
});

// Rule 20: only the Engineer role can call the method, in a session, on an encrypted channel
const resetMethod = namespace.addMethod(machine, {
    browseName: "Reset",
    rolePermissions: [
        { roleId: WellKnownRoleIds.Engineer, permissions: PermissionType.Browse | PermissionType.Call }
    ],
    accessRestrictions: AccessRestrictionsFlag.EncryptionRequired | AccessRestrictionsFlag.SessionRequired
});
resetMethod.bindMethod(async () => ({ statusCode: StatusCodes.Good }));

await server.start();

More information: OPC UA role-based security with NodeOPCUA, What is the OPC UA GDS? and the OPC UA applicationUri and certificate identity.

5.1 Behind NAT, Docker or a TCP proxy

This example applies rules 14 to 17. See also Advertised Endpoints in the node-opcua documentation. The server listens on 10.20.0.10:4840. The clients in the OT zone use opc.tcp://gw01.ot.example.com:4840. A NAT gateway forwards opc.tcp://gw01.example.com:48400 to the server.

OPCUAServer.requestExactEndpointUrl = true;               // rule 16: refuse unknown URLs

const server = new OPCUAServer({
    host: "10.20.0.10",                                     // rule 14: listen on the OT interface only
    port: 4840,
    hostname: "gw01.ot.example.com",                        // rule 15: the name that the OT clients use
    alternateHostname: ["10.20.0.10"],                      // rule 15: clients that use the IP address
    advertisedEndpoints: ["opc.tcp://gw01.example.com:48400"], // rule 15: the external NAT address
    securityModes: [MessageSecurityMode.SignAndEncrypt],    // rule 17: one security level for all URLs
    securityPolicies: [SecurityPolicy.Basic256Sha256, SecurityPolicy.Aes256_Sha256_RsaPss],
    allowAnonymous: false,
    serverCertificateManager,
    userCertificateManager
});

The certificate of the server must contain gw01.ot.example.com, gw01.example.com and 10.20.0.10 in its subjectAltName.

5.2 Two networks with different security

If two networks must have different security, make two listeners (rule 17). Each entry in endpoints must have its own security settings. In node-opcua 2.187.22, all the listeners use the top-level host (rule 14). Thus, separate the networks with the port numbers and the firewall.

const server = new OPCUAServer({
    serverCertificateManager,
    userCertificateManager,
    endpoints: [
        {
            // OT zone: operators and engineers
            port: 4840,
            hostname: "gw01.ot.example.com",
            securityModes: [MessageSecurityMode.SignAndEncrypt],
            securityPolicies: [SecurityPolicy.Basic256Sha256],
            allowAnonymous: false
        },
        {
            // DMZ: the historian only. Open port 4841 only from the historian in the firewall
            port: 4841,
            hostname: "gw01.dmz.example.com",
            securityModes: [MessageSecurityMode.SignAndEncrypt],
            securityPolicies: [SecurityPolicy.Aes256_Sha256_RsaPss],
            allowAnonymous: false
        }
    ]
});

6. Frequently asked questions

Is a node-opcua server secure by default?

No. The default settings are for development. By default, the server accepts anonymous users. It shows endpoints with the security mode None. The default certificate stores accept all unknown certificates automatically. Apply the 20 rules of this guide before production.

How do I secure a node-opcua server?

Use the latest node-opcua version. Give a dedicated OPCUACertificateManager with automaticallyAcceptUnknownCertificate: false to the server and to the user certificates. Set allowAnonymous: false and use the SignAndEncrypt security mode only. Prefer X.509 user certificates to passwords. Run the server as a dedicated non-root user, and give the PKI folder to this user only.

Why must I not use automaticallyAcceptUnknownCertificate in production?

With automaticallyAcceptUnknownCertificate: true, the server trusts the certificate of all clients. Then the certificate check does not protect the server. Set it to false, and put only the certificates or CAs that you trust in the trusted folder.

How do I use X.509 user certificates instead of passwords?

Give a dedicated OPCUACertificateManager to userCertificateManager, with automaticallyAcceptUnknownCertificate: false. Put the user certificates, or their CA, in its trusted folder. Then give a userManager that refuses all passwords.

Which file permissions must the PKI folder have?

Give the PKI folder to the dedicated user that runs the server. Set the folders to 700 and the files to 600. Set UMask=0077 in the systemd unit, so that new files also get 600.

Are node-opcua variables writable by default?

Yes. If you do not set accessLevel, addVariable uses CurrentRead | CurrentWrite. Always set accessLevel: "CurrentRead" on the variables that clients only read. On a writable variable, give the Write permission to specified roles only, with rolePermissions.

How do I configure a node-opcua server behind NAT or Docker?

Keep the listener on its local host and port. Add the external URL with advertisedEndpoints. Put the external name in the subjectAltName of the server certificate. Set OPCUAServer.requestExactEndpointUrl = true to refuse the URLs that the server does not advertise.

Why use a GDS instead of self-signed certificates?

A self-signed certificate must be trusted manually on each peer, and node-opcua makes it valid for 10 years by default. There is no revocation and no automatic renewal. A GDS issues CA certificates with a short validity and renews them automatically. It also sends the trust lists and the CRLs to all applications.

7. References

Related Sterfive articles

Get help

Sterfive maintains node-opcua. We give OPC UA security training, programming with node-opcua training and support. Contact us.