angular-cli icon indicating copy to clipboard operation
angular-cli copied to clipboard

Dev server with SSR not accepting self-signed certificate

Open mjmustafaev opened this issue 1 month ago • 4 comments

Which @angular/* package(s) are the source of the bug?

compiler

Is this a regression?

Yes

Description

Getting the below error, even though the self-signed certificate is trusted. Happening in SSR.

Error object: HttpErrorResponse {
  headers: _HttpHeaders {                                                                                                                                                        
    headers: Map(0) {},                                                                                                                                                          
    normalizedNames: Map(0) {},                                                                                                                                                  
    lazyInit: undefined,                                                                                                                                                         
    lazyUpdate: null                                                                                                                                                             
  },                                                                                                                                                                             
  status: 0,                                                                                                                                                                     
  statusText: 'Unknown Error',                                                                                                                                                   
  url: 'https://localhost:7257/articles',                                                                                                       
  ok: false,                                                                                                                                                                     
  type: undefined,                                                                                                                                                               
  redirected: undefined,                                                                                                                                                         
  responseType: undefined,                                                                                                                                                       
  name: 'HttpErrorResponse',                                                                                                                                                     
  message: 'Http failure response for https://localhost:7257/articles 0 undefined',                                                            
  error: TypeError: fetch failed                                                                                                                                                 
      at node:internal/deps/undici/undici:15845:13                                                                                                                               
      at process.processTicksAndRejections (node:internal/process/task_queues:103:5) {                                                                                           
    [cause]: Error: self-signed certificate; if the root CA is installed locally, try running Node.js with --use-system-ca                                                       
        at TLSSocket.onConnectSecure (node:_tls_wrap:1631:34)                                                                                                                    
        at TLSSocket.emit (node:events:508:28)                                                                                                                                   
        at TLSSocket._finishInit (node:_tls_wrap:1077:8)                                                                                                                         
        at ssl.onhandshakedone (node:_tls_wrap:863:12) {                                                                                                                         
      code: 'DEPTH_ZERO_SELF_SIGNED_CERT'                                                                                                                                        
    }                                                                                                                                                                            
  }                                                                                                                                                                              
}

Only workaround I found so far is to set process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = '0'

Setting NODE_EXTRA_CA_CERTS didn't help.

Please provide a link to a minimal reproduction of the bug

Please provide the exception or error you saw


Please provide the environment you discovered this bug in (run ng version)

Angular CLI       : 21.0.0
Angular           : 21.0.0
Node.js           : 24.11.1
Package Manager   : npm 11.6.2
Operating System  : darwin arm64

┌───────────────────────────┬───────────────────┬───────────────────┐
│ Package                   │ Installed Version │ Requested Version │
├───────────────────────────┼───────────────────┼───────────────────┤
│ @angular/animations       │ 21.0.0            │ ^21.0.0           │
│ @angular/build            │ 21.0.0            │ ^21.0.0           │
│ @angular/cli              │ 21.0.0            │ ^21.0.0           │
│ @angular/common           │ 21.0.0            │ ^21.0.0           │
│ @angular/compiler         │ 21.0.0            │ ^21.0.0           │
│ @angular/compiler-cli     │ 21.0.0            │ ^21.0.0           │
│ @angular/core             │ 21.0.0            │ ^21.0.0           │
│ @angular/forms            │ 21.0.0            │ ^21.0.0           │
│ @angular/platform-browser │ 21.0.0            │ ^21.0.0           │
│ @angular/platform-server  │ 21.0.0            │ ^21.0.0           │
│ @angular/router           │ 21.0.0            │ ^21.0.0           │
│ @angular/ssr              │ 21.0.0            │ ^21.0.0           │
│ rxjs                      │ 7.8.2             │ ~7.8.0            │
│ typescript                │ 5.9.3             │ ~5.9.3            │
└───────────────────────────┴───────────────────┴───────────────────┘

Anything else?

No response

mjmustafaev avatar Dec 01 '25 12:12 mjmustafaev

Can you please try to use the latest version and see if the problem persists?

alan-agius4 avatar Dec 01 '25 12:12 alan-agius4

Hello @alan-agius4,

Just updated. The error is the same as below, even though NODE_EXTRA_CA_CERTS is set.

Double-checked through the console log with: console.log('SSR NODE_EXTRA_CA_CERTS =', process.env['NODE_EXTRA_CA_CERTS']);

Error:

Error object: HttpErrorResponse {
  headers: _HttpHeaders {                                                                                                                                                        
    headers: Map(0) {},                                                                                                                                                          
    normalizedNames: Map(0) {},                                                                                                                                                  
    lazyInit: undefined,                                                                                                                                                         
    lazyUpdate: null                                                                                                                                                             
  },                                                                                                                                                                             
  status: 0,                                                                                                                                                                     
  statusText: 'Unknown Error',                                                                                                                                                   
  url: 'https://localhost:7257/articles',                                                                                                       
  ok: false,                                                                                                                                                                     
  type: undefined,                                                                                                                                                               
  redirected: undefined,                                                                                                                                                         
  responseType: undefined,                                                                                                                                                       
  name: 'HttpErrorResponse',                                                                                                                                                     
  message: 'Http failure response for https://localhost:7257/articles: 0 undefined',                                                            
  error: TypeError: fetch failed                                                                                                                                                 
      at node:internal/deps/undici/undici:15845:13                                                                                                                               
      at process.processTicksAndRejections (node:internal/process/task_queues:103:5) {                                                                                           
    [cause]: Error: self-signed certificate; if the root CA is installed locally, try running Node.js with --use-system-ca                                                       
        at TLSSocket.onConnectSecure (node:_tls_wrap:1631:34)                                                                                                                    
        at TLSSocket.emit (node:events:508:28)                                                                                                                                   
        at TLSSocket._finishInit (node:_tls_wrap:1077:8)                                                                                                                         
        at ssl.onhandshakedone (node:_tls_wrap:863:12) {                                                                                                                         
      code: 'DEPTH_ZERO_SELF_SIGNED_CERT'                                                                                                                                        
    }                                                                                                                                                                            
  }                                                                                                                                                                              
}

Version:

Angular CLI       : 21.0.1
Angular           : 21.0.2
Node.js           : 24.11.1
Package Manager   : npm 11.6.2
Operating System  : darwin arm64

┌───────────────────────────┬───────────────────┬───────────────────┐
│ Package                   │ Installed Version │ Requested Version │
├───────────────────────────┼───────────────────┼───────────────────┤
│ @angular/animations       │ 21.0.2            │ ^21.0.2           │
│ @angular/build            │ 21.0.1            │ ^21.0.1           │
│ @angular/cli              │ 21.0.1            │ ^21.0.1           │
│ @angular/common           │ 21.0.2            │ ^21.0.2           │
│ @angular/compiler         │ 21.0.2            │ ^21.0.2           │
│ @angular/compiler-cli     │ 21.0.2            │ ^21.0.2           │
│ @angular/core             │ 21.0.2            │ ^21.0.2           │
│ @angular/forms            │ 21.0.2            │ ^21.0.2           │
│ @angular/platform-browser │ 21.0.2            │ ^21.0.2           │
│ @angular/platform-server  │ 21.0.2            │ ^21.0.2           │
│ @angular/router           │ 21.0.2            │ ^21.0.2           │
│ @angular/ssr              │ 21.0.1            │ ^21.0.1           │
│ rxjs                      │ 7.8.2             │ ~7.8.0            │
│ typescript                │ 5.9.3             │ ~5.9.3            │
└───────────────────────────┴───────────────────┴───────────────────┘

mjmustafaev avatar Dec 01 '25 12:12 mjmustafaev

This seems like a bug but we'll need to look at a reproduction to find and fix the problem. Can you setup a minimal repro please?

You can read here why this is needed. A good way to make a minimal repro is to create a new app via ng new repro-app and adding the minimum possible code to show the problem. Then you can push this repository to github and link it here.

This might be related to your directory structure so its really important to get an accurate repro to diagnose this.

alan-agius4 avatar Dec 01 '25 13:12 alan-agius4

@alan-agius4, got it. Yes, I can do that. Give me some time, and I'll share the repo link here soon. I need to create a minimal repro for our .NET backend too, as it's happening when the Angular component is making an HTTP call to the backend APIs for the SSR route.

mjmustafaev avatar Dec 01 '25 23:12 mjmustafaev

Hey @alan-agius4, here's the link for the repro:

https://github.com/mjmustafaev/angular-ssr-dotnet-selfsigned-repro

Below are the steps on how to reproduce:

Prerequisites

Make sure you have (latest stable):

  • .NET SDK
  • Node.js
  • Angular CLI
npm install -g @angular/cli@21

1. Create a clean repro repository

mkdir angular-ssr-dotnet-selfsigned-repro
cd angular-ssr-dotnet-selfsigned-repro
git init

Create folders for backend and frontend:

mkdir backend
mkdir frontend

2. Backend – .NET HTTPS WebAPI (WeatherForecast)

2.1 Generate the WebAPI

cd backend
dotnet new webapi -n BackendApi
cd BackendApi

This creates the default WeatherForecast endpoint at /weatherforecast.

2.2 Trust the developer HTTPS certificate

Run:

dotnet dev-certs https --trust

Confirm in your OS (e.g. Keychain on macOS) that the certificate is trusted.

2.3 Enable permissive CORS

In Program.cs, add CORS and use it:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy("AllowAll", policy =>
    {
        policy
            .AllowAnyOrigin()
            .AllowAnyMethod()
            .AllowAnyHeader();
    });
});

// Add services to the container.
builder.Services.AddOpenApi();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.MapOpenApi();
}

app.UseCors("AllowAll");

app.UseHttpsRedirection();

// WeatherForecast endpoint (default template)
var summaries = new[]
{
    "Freezing", "Bracing", "Chilly", "Cool", "Mild",
    "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};

app.MapGet("/weatherforecast", () =>
{
    var forecast = Enumerable.Range(1, 5).Select(index =>
        new WeatherForecast
        (
            DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
            Random.Shared.Next(-20, 55),
            summaries[Random.Shared.Next(summaries.Length)]
        ))
        .ToArray();
    return forecast;
})
.WithName("GetWeatherForecast");

app.Run();

record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary)
{
    public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}

2.4 Run the backend on HTTPS

Run with the HTTPS profile (the default template has one):

dotnet run --launch-profile https

Note the HTTPS URL printed in the console, e.g.:

https://localhost:7257

Check that the API works in the browser:

https://localhost:7257/weatherforecast

3. Frontend – Angular 21 SSR app

Open a new terminal tab/window, then:

cd /path/to/angular-ssr-dotnet-selfsigned-repro
cd frontend

3.1 Create Angular SSR app

Create the app with SSR enabled:

ng new ssr-client --routing --style=scss --ssr
cd ssr-client
npm install

4. Create a Weather component that calls the HTTPS API

4.1 Generate the component

ng g c weather --inline-template --inline-style

4.2 Implement the component

Edit src/app/weather/weather.ts:

import { Component, signal } from '@angular/core';
import { HttpClient } from '@angular/common/http';

interface WeatherForecast {
  date: string;
  temperatureC: number;
  summary?: string;
}

@Component({
  selector: 'app-weather',
  standalone: true,
  imports: [],
  template: `
    <div class="p-4">
      <h1>Weather from .NET API</h1>

      @if (error()) {
        <p style="color: red">Error: {{ error() }}</p>
      } @else if (!data()) {
        <p>Loading...</p>
      } @else {
        <ul>
          @for (item of data(); track item.date) {
            <li>
              {{ item.date }} – {{ item.temperatureC }} °C – {{ item.summary || 'n/a' }}
            </li>
          }
        </ul>
      }
    </div>
  `
})
export class Weather {
  data = signal<WeatherForecast[] | null>(null);
  error = signal<string | null>(null);

  constructor(http: HttpClient) {
    http.get<WeatherForecast[]>('https://localhost:7257/weatherforecast')
      .subscribe({
        next: (result) => this.data.set(result),
        error: (err) => {
          console.error('HTTP error', err);
          this.error.set(err?.message ?? 'Unknown error');
        }
      });
  }
}

Important: Adjust https://localhost:7257 if your .NET HTTPS port is different.

4.3 Make WeatherComponent the root route

Edit src/app/app.routes.ts:

import { Routes } from '@angular/router';
import { Weather } from './weather/weather';

export const routes: Routes = [
  {
    path: '',
    component: Weather,
  },
];

5. Run Angular dev server over HTTPS

From frontend/ssr-client:

mkdir certs
cd certs
openssl req -x509 -newkey rsa:2048 -nodes   -keyout angular-dev.key   -out angular-dev.crt   -days 365   -subj "/CN=localhost"
cd ..

Run Angular dev server with HTTPS:

ng serve   --ssl true   --ssl-cert ../certs/angular-dev.crt   --ssl-key ../certs/angular-dev.key

Angular now serves:

https://localhost:4200/

The SSR error will reproduce when Angular calls the HTTPS backend.


6. Trigger the issue

With:

  • .NET backend running (dotnet run --launch-profile https)
  • Angular dev server running (ng serve ...)

Open in the browser:

https://localhost:4200/

On first load, Angular SSR attempts to execute:

http.get('https://localhost:7257/weatherforecast')

6.1 Browser behaviour

  • Directly opening https://localhost:7257/weatherforecast in the browser works.
  • After hydration, client-side HTTP calls from Angular succeed (certificate trusted).

6.2 SSR / Node behaviour

In the Angular dev server console, you see errors similar to:

HTTP error HttpErrorResponse {
  status: 0,
  statusText: 'Unknown Error',
  url: 'https://localhost:7257/weatherforecast',
  message: 'Http failure response for https://localhost:7257/weatherforecast: 0 undefined',
  error: TypeError: fetch failed
    at node:internal/deps/undici/undici:...
    ...
    [cause]: Error: self-signed certificate; if the root CA is installed locally,
    try running Node.js with --use-system-ca {
      code: 'DEPTH_ZERO_SELF_SIGNED_CERT'
    }
}

This shows:

  • Browser trusts and calls the API fine.
  • SSR (Node + undici/fetch) rejects the same certificate with DEPTH_ZERO_SELF_SIGNED_CERT.

mjmustafaev avatar Dec 07 '25 21:12 mjmustafaev