Following another set of compromised Node-RED servers that were open to the Internet, I realised that there might be some useful advice missing from the above.
There is an additonal level of security that you can apply and indeed, really should, if you need to expose a Node-RED service endpoint (e.g. a web page or API) to the Internet.
That is that you should consider having a separate instance of Node-RED on a non-standard IP port, preferably on a separate server (a Raspberry Pi might be sufficient).
A separate instance would be configured to do as little as possible itself other than handle incoming data from an instance (or MQTT or other disaggregating service) that is not exposed to the Internet and present the required endpoint(s). You could also allow data to cross over to the more secure instance - BUT, make sure that BOTH instances validate any input since ALL input across the Internet (and indeed any user input anyway) MUST be carefully validated before use.
For any static configuration on the Internet-facing instance, ideally you should periodically check against a known good config (you must run the check from something that isn't accessible from the Internet of course).
Using a separate service for this is ideal because it allows you to create physical separation not possible when running on a single server. It also allows you to minimise the attack surface by removing all unnecessary services and locking the device down carefully. Data should never be mastered on the Internet-facing server and configurations should be pushed from a non-Internet facing source so that even if the internet-facing server is compromised, it can fairly safely be reset.
This is an example of a "DMZ" approach (De-Militarised Zone) that is required in all enterprise, commercial and regulated deployments. In the case of a full DMZ, the externally-facing server(s) would have a separate LAN and router/firewall between them and the more secure internal networks. This can be simulated in a low-cost setup by using a VLAN (if your router/switch supports it) or at least using a local server firewall (e.g. IPTABLES, UFW, etc) on the internal server.