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
- Upgrade node-opcua each month.
- Do not run the server as
root. - Restrict the PKI folder to the server user.
- Keep the audit events.
Certificates and PKI
- Use a dedicated
OPCUACertificateManagerfor the server. - Set
automaticallyAcceptUnknownCertificate: false. - Encrypt the private key with
privateKeyPassphrase. - Advanced: keep the private key in a TPM or an HSM.
- Use CA certificates from a GDS, with a short validity and automatic renewal. Do not use self-signed certificates.
Communication, endpoints and authentication
- Use
SignAndEncryptonly. RemoveNoneandBasic128Rsa15. - Set
allowAnonymous: false. - Use X.509 user certificates, not passwords.
- Limit the sessions and the connections.
- Listen only on the OT interface (
host). Always setport. - Show only the endpoint URLs that clients use. Put all of them in the certificate.
- Refuse unknown endpoint URLs (
requestExactEndpointUrl). - Set the security on each listener. Per-URL overrides are not a barrier.
Address space and authorization
- Give roles to users (OPC UA Part 18).
- Make variables read-only, unless a client must write them. Always set
accessLevel. - Give
Callon 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 withoutaccessLevelis writable. Change these settings before you use the server in production.
2. The basic terms
Read these terms first. The rules use them.
| Term | What it is |
|---|---|
| Endpoint | An address and a security configuration that the server shows to the clients. A client selects one endpoint to connect. |
| Security mode | The protection of the messages: None (no protection), Sign (integrity) or SignAndEncrypt (integrity and confidentiality). |
| Security policy | The set of algorithms for the signature and the encryption, for example Basic256Sha256. |
| Certificate manager | The 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 token | The proof of the user identity in a session: anonymous, user name and password, or X.509 user certificate. |
| Role | A group of permissions, for example Observer, Operator or Engineer. The server gives roles to users. |
| Access level | The attribute of a variable that tells if a client can read it (CurrentRead) or write it (CurrentWrite). |
| Method | A 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.
- Upgrade. Always use the latest node-opcua version. Examine your dependencies with
npm outdatedandnpm auditeach month. Reason: each release contains security corrections. - 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 needroot. - Restrict the PKI folder. Give the PKI folder to the user that runs the server. Set the folder permissions to
700and the file permissions to600. 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. - 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.
- Use a dedicated certificate manager. Give an
OPCUACertificateManagerwith its ownrootFoldertoserverCertificateManager. 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. - Do not accept unknown certificates automatically. Set
automaticallyAcceptUnknownCertificate: false. Put each trusted client certificate, or its CA, in thetrustedfolder. Reason: the default stores trust all clients. Then the certificate check does not protect the server. - Protect the private key. Set
privateKeyPassphraseto encrypt the key on the disk. Reason: a copy of the key file then does not give the identity of the server. - Advanced: keep the private key in hardware. Set
keyOperationsto 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. - 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 certificates | CA certificates from a GDS | |
|---|---|---|
| Trust | Each certificate on each peer | One CA certificate on each peer |
| Validity | 10 years (node-opcua default) | Short, for example one year |
| Renewal | Manual | Automatic, before the expiry date |
| Revocation | Manual removal from all trust lists | CRL that the GDS sends to all applications |
| Trust list update | Manual, on each application | Sent by the GDS (OPC 10000-12) |
| Record of the operations | None | Central 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
- Install the package
node-opcua-server-configuration. - Initialize the server.
- Call
installPushCertificateManagementOnServer. This adds theServerConfigurationobject of OPC 10000-12 to the address space. - Give the
SecurityAdminrole only to the identity of the GDS. Only this role can call the methods ofServerConfiguration. - 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.
- Use secure connections only. Set
securityModestoSignAndEncrypt. RemoveNoneandBasic128Rsa15fromsecurityPolicies. Reason: withNone, an attacker can read and change the messages.Basic128Rsa15is an old and weak policy. - Refuse anonymous users. Set
allowAnonymous: false. Reason: the default value istrue. Then all clients can open a session without an identity. - Use X.509 user certificates, not passwords. Give a second dedicated
OPCUACertificateManagertouserCertificateManager. Set itsautomaticallyAcceptUnknownCertificatetofalsetoo. If you do not use passwords, give auserManagerthat refuses all passwords. Reason: a certificate is more difficult to steal or guess than a password. - Limit the resources. Set
serverCapabilities.maxSessionsandmaxConnectionsPerEndpointto the values that your clients use. Reason: the limits prevent the exhaustion of the server. - 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, sethostto the IP address of the OT network interface. By default, node-opcua listens on all interfaces (::or0.0.0.0). In node-opcua 2.187.22, thehostof an entry inendpointshas no effect: all listeners use the top-levelhost. Reason: the server is then not reachable from the other networks, for example the office network. - Show only the endpoint URLs that the clients use. Set
hostnameto the name that the clients use. Add other names or IP addresses withalternateHostname. Behind NAT, Docker published ports or a TCP proxy, add the external URL withadvertisedEndpoints. The server still listens onport. 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. - Refuse the unknown endpoint URLs. Set
OPCUAServer.requestExactEndpointUrl = true, or the environment variableNODEOPCUA_SERVER_REQUEST_EXACT_ENDPOINT_URL=1. The server then refuses a CreateSession with an endpoint URL that it does not advertise (BadServiceUnsupported). WithisAuditing: true, the server also raises anAuditUrlMismatchEventTypeevent. Reason: by default, the server accepts an unknown URL and uses the first endpoint that agrees with the security settings. - Set the security on each listener, and do not use per-URL overrides as a barrier. The entries in
endpointsdo not inherit the top-level security settings. SetsecurityModes,securityPoliciesandallowAnonymousin each entry. All the URLs inadvertisedEndpointsshare one TCP listener. Thus, theirsecurityModes,securityPoliciesandallowAnonymousoverrides 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 inendpointsuses the insecure defaults.
3.4 Address space and authorization
These rules control what each user can see, read, write and call.
- Give roles, not full access. Install the OPC UA role-based security (Part 18) with
createRoleBasedSecurityfromnode-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, setunresolvedPermissionPolicy: "deny". Reason: each user gets only the permissions that are necessary for the task. - Make variables read-only, unless a client must write them. Always set
accessLevelon each variable. If you do not set it,addVariableusesCurrentRead | CurrentWrite. Then all users can write the variable. SetaccessLevel: "CurrentRead"on the variables that clients only read. SetCurrentWriteonly on the variables that a client must change. On these variables, setrolePermissionsthat giveWriteto specified roles only. Do the same for the variables that the server keeps in persistent storage. Do not useuserAccessLevelto control the users: it has the same value for all users. Reason: a writable variable can change the process or the stored configuration. - Protect the methods. On each method, set
rolePermissionsthat giveCallto specified roles only. SetaccessRestrictionstoEncryptionRequiredandSessionRequired. Reason: by default (unresolvedPermissionPolicy: "allow"), all users can call a method that has norolePermissions. 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
- OPC 10000-4, clause 6.1.2, ApplicationInstanceCertificate: reference.opcfoundation.org
- OPC 10000-18, Role-Based Security: reference.opcfoundation.org
- node-opcua source code: github.com/node-opcua/node-opcua
- node-opcua-server-configuration (push certificate management): README
- node-opcua-role-set-server (role-based security): README, Role-Based Security and User Management guide
- Using an HSM/KMS-held application key: documentation
- Advertised Endpoints: documentation
- OPC 10000-12, Discovery and Global Services: reference.opcfoundation.org
- OWASP Docker Security Cheat Sheet: owasp.org
- OWASP Node.js Security Cheat Sheet: owasp.org
- Node.js Best Practices, run Node.js as non-root user: github.com
- NIST SP 800-82 Rev. 3, Guide to Operational Technology (OT) Security: csrc.nist.gov
- ASD-STE100 Simplified Technical English: asd-ste100.org
Related Sterfive articles
- Secure Node-RED in production: a 101 guide with recipes
- OPC UA role-based security with NodeOPCUA
- What is the OPC UA GDS?
- The OPC UA applicationUri and certificate identity
Get help
Sterfive maintains node-opcua. We give OPC UA security training, programming with node-opcua training and support. Contact us.