Skip to content

Angular SSR - PM2 cluster support #31387

Closed as not planned
Closed as not planned

Description

@hheexx

Which @angular/* package(s) are relevant/related to the feature request?

platform-server

Description

Currently to scale Angular SSR beyond 1 core if you use PM2 you must have nginx in front with configured upstreams on multiple ports. PM2 configuration is also complicated with increment_var setting for port. Any change to a number of instances requires changing nginx configuration.

Proposed solution

Angular SSR should be able to run in PM2 cluster mode for easy scaling

Alternatives considered

/

Activity

  1. transferred this issue fromangular/angularon Oct 4, 2025
  2. alan-agius4 commented on Oct 15, 2025

    @alan-agius4
    Collaborator

    Regardless of using clustering or not. It's highly recommended to place a load balancer like Nginx in front of your Node.js application. Direct traffic should generally never reach the Node.js server itself.

    PM2 and Cluster Mode

    When using PM2 in cluster mode, or utilizing the standard Node.js cluster module, all worker processes typically share the same network port. The primary process (or PM2) acts as a load balancer and manages the distribution of incoming connections to these workers. Why do you want to have different ports?

    I tried PM2 clustering with Angular SSR, and it worked right away without needing any changes.

    Clustering Implementation Example without PM2

    Implementing basic clustering is also straightforward using the native Node.js cluster API. The following code snippet demonstrates a primary process forking workers and ensuring a dead worker is replaced:

    if (cluster.isPrimary) {
      // Primary/Master process logic
      console.log(`Master process ${process.pid} is running`);
    
      // Fork workers based on CPU count
      for (let i = 0; i < numCPUs; i++) {
        cluster.fork();
      }
    
      // Auto-restart failed workers
      cluster.on('exit', (worker, code, signal) => {
        console.log(`Worker ${worker.process.pid} died. Spawning a new one.`);
        cluster.fork(); // Spawn a replacement worker
      });
    } else {
      // Worker process logic (listens for requests)
      const port = 4000;
      app.listen(port, (error) => {
        if (error) {
          throw error;
        }
        console.log(`Node Express server listening on http://localhost:${port} (Worker ${process.pid})`);
      });
    }
  3. hheexx commented on Oct 15, 2025

    @hheexx
    Author

    @alan-agius4 I fully agree nginx should be in front. I believe it's not stated in documentation.

    Why do you want to have different ports?

    I don't understand the question. I don't want to have multiple ports but I need to have them because cluster mode is not supported and in fork mode each instance must have it's own port.

    I tried PM2 clustering with Angular SSR, and it worked right away without needing any changes.

    I tried few months ago and it would just hang on start. Also I found references on the internet that it's not supported.
    Are you sure it works for you? If yes I will test again.

  4. alan-agius4 commented on Oct 15, 2025

    @alan-agius4
    Collaborator

    @hheexx, yes I worked when using the latest version to scaffold the project.

  5. hheexx commented on Oct 17, 2025

    @hheexx
    Author

    I tried now with my project that uses stock server.ts and it does not work.
    Application enters restart loop with status errored end no log output.

  6. alan-agius4 commented on Oct 17, 2025

    @alan-agius4
    Collaborator

    What is your PM2 config? I tried again and it just works out of the box.

    $ ng new ssr-cluster --ssr
    $ cd ssr-cluster
    $ ng b
    

    ecosystem.config.js

    module.exports = {
      apps: [
        {
          name: "cluster",
          script: "./dist/ssr-cluster/server/server.mjs",
          instances: "5", 
          exec_mode: "cluster",
        },
      ],
    };
    $ npx pm2 start ecosystem.config.js
    [PM2] Spawning PM2 daemon with pm2_home=/xxxxxxx/.pm2
    [PM2] PM2 Successfully daemonized
    [PM2][WARN] Applications cluster not running, starting...
    [PM2] App [cluster] launched (5 instances)
    ┌────┬────────────────────┬──────────┬──────┬───────────┬──────────┬──────────┐
    │ id │ name               │ mode     │ ↺    │ status    │ cpu      │ memory   │
    ├────┼────────────────────┼──────────┼──────┼───────────┼──────────┼──────────┤
    │ 0  │ cluster            │ cluster  │ 0    │ online    │ 0%       │ 65.5mb   │
    │ 1  │ cluster            │ cluster  │ 0    │ online    │ 0%       │ 63.0mb   │
    │ 2  │ cluster            │ cluster  │ 0    │ online    │ 0%       │ 57.5mb   │
    │ 3  │ cluster            │ cluster  │ 0    │ online    │ 0%       │ 53.9mb   │
    │ 4  │ cluster            │ cluster  │ 0    │ online    │ 0%       │ 51.8mb   │
    └────┴────────────────────┴──────────┴──────┴───────────┴──────────┴──────────┘
    
    $ curl http://localhost:4000 -I
    HTTP/1.1 200 OK
    X-Powered-By: Express
    content-length: 21576
    content-type: text/html;charset=UTF-8
    etag: "e80dbacaf06858bd2e5f221570e342a7e7b88cff271b023f2f7dc55b232a4012"
    Date: Fri, 17 Oct 2025 15:21:40 GMT
    Connection: keep-alive
    Keep-Alive: timeout=5
    
  7. alan-agius4 commented on Oct 17, 2025

    @alan-agius4
    Collaborator

    Which version of Angular CLI are using to generate the project? (i.e. the globally installed version). Maybe that’s outdated?

  8. hheexx commented on Oct 17, 2025

    @hheexx
    Author

    Now I tried with ng new project and it works like you said. yey...

    For some reason it does not work with my project that also uses latest angular and stock server.ts.
    Looks like something from inside angular project is preventing it to ran and that's strange....

  9. alan-agius4 commented on Oct 17, 2025

    @alan-agius4
    Collaborator

    Check that you have the following condition:

    if (isMainModule(import.meta.url) || process.env['pm_id']) {

    Closing since in an ng new project clustering works out of the box.

  10. hheexx commented on Oct 20, 2025

    @hheexx
    Author

    Found it!
    It's not Angular related.

    It was about permissions on a logs folder.
    In fork mode logs are written by pm2 process, in cluster mode it's run by child process.
    On my server those are not same users.

  11. angular-automatic-lock-bot commented on Nov 20, 2025

    @angular-automatic-lock-bot

    This issue has been automatically locked due to inactivity.
    Please file a new issue if you are encountering a similar or related problem.

    Read more about our automatic conversation locking policy.

    This action has been performed automatically by a bot.

  12. locked and limited conversation to collaborators on Nov 20, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions