diff --git a/.eslintrc.js b/.eslintrc.js
index 5a63c79371c984..43e41026b2007e 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -328,6 +328,7 @@ module.exports = {
     DecompressionStream: 'readable',
     fetch: 'readable',
     FormData: 'readable',
+    navigator: 'readable',
     ReadableStream: 'readable',
     ReadableStreamDefaultReader: 'readable',
     ReadableStreamBYOBReader: 'readable',
diff --git a/doc/api/globals.md b/doc/api/globals.md
index 8285064c2e25e4..83aa62c86ea790 100644
--- a/doc/api/globals.md
+++ b/doc/api/globals.md
@@ -583,6 +583,41 @@ The `MessagePort` class. See [`MessagePort`][] for more details.
 
 This variable may appear to be global but is not. See [`module`][].
 
+## `Navigator`
+
+<!-- YAML
+added: REPLACEME
+-->
+
+> Stability: 1 - Experimental
+
+An implementation of the [Navigator API][].
+
+## `navigator`
+
+<!-- YAML
+added: REPLACEME
+-->
+
+> Stability: 1 - Experimental
+
+An implementation of [`window.navigator`][].
+
+### `navigator.hardwareConcurrency`
+
+<!-- YAML
+added: REPLACEME
+-->
+
+* {number}
+
+The `navigator.hardwareConcurrency` read-only property returns the number of
+logical processors available to the current Node.js instance.
+
+```js
+console.log(`This process is running on ${navigator.hardwareConcurrency}`);
+```
+
 ## `PerformanceEntry`
 
 <!-- YAML
@@ -998,6 +1033,7 @@ A browser-compatible implementation of [`WritableStreamDefaultWriter`][].
 
 [CommonJS module]: modules.md
 [ECMAScript module]: esm.md
+[Navigator API]: https://html.spec.whatwg.org/multipage/system-state.html#the-navigator-object
 [Web Crypto API]: webcrypto.md
 [`--no-experimental-fetch`]: cli.md#--no-experimental-fetch
 [`--no-experimental-global-customevent`]: cli.md#--no-experimental-global-customevent
@@ -1057,6 +1093,7 @@ A browser-compatible implementation of [`WritableStreamDefaultWriter`][].
 [`setInterval`]: timers.md#setintervalcallback-delay-args
 [`setTimeout`]: timers.md#settimeoutcallback-delay-args
 [`structuredClone`]: https://developer.mozilla.org/en-US/docs/Web/API/structuredClone
+[`window.navigator`]: https://developer.mozilla.org/en-US/docs/Web/API/Window/navigator
 [buffer section]: buffer.md
 [built-in objects]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects
 [module system documentation]: modules.md
diff --git a/lib/.eslintrc.yaml b/lib/.eslintrc.yaml
index cc4fa1975016eb..2b77dce6967d7b 100644
--- a/lib/.eslintrc.yaml
+++ b/lib/.eslintrc.yaml
@@ -75,6 +75,10 @@ rules:
       message: Use `const { MessageEvent } = require('internal/worker/io');` instead of the global.
     - name: MessagePort
       message: Use `const { MessagePort } = require('internal/worker/io');` instead of the global.
+    - name: Navigator
+      message: Use `const { Navigator } = require('internal/navigator');` instead of the global.
+    - name: navigator
+      message: Use `const { navigator } = require('internal/navigator');` instead of the global.
     - name: PerformanceEntry
       message: Use `const { PerformanceEntry } = require('perf_hooks');` instead of the global.
     - name: PerformanceMark
diff --git a/lib/internal/bootstrap/web/exposed-window-or-worker.js b/lib/internal/bootstrap/web/exposed-window-or-worker.js
index 8dc77493e1f152..3cc555b82f35a7 100644
--- a/lib/internal/bootstrap/web/exposed-window-or-worker.js
+++ b/lib/internal/bootstrap/web/exposed-window-or-worker.js
@@ -2,7 +2,7 @@
 
 /**
  * This file exposes web interfaces that is defined with the WebIDL
- * Exposed=(Window,Worker) extended attribute or exposed in
+ * Exposed=Window + Exposed=(Window,Worker) extended attribute or exposed in
  * WindowOrWorkerGlobalScope mixin.
  * See more details at https://webidl.spec.whatwg.org/#Exposed and
  * https://html.spec.whatwg.org/multipage/webappapis.html#windoworworkerglobalscope.
@@ -55,6 +55,10 @@ exposeLazyInterfaces(globalThis, 'perf_hooks', [
 
 defineReplaceableLazyAttribute(globalThis, 'perf_hooks', ['performance']);
 
+// https://html.spec.whatwg.org/multipage/system-state.html#the-navigator-object
+exposeLazyInterfaces(globalThis, 'internal/navigator', ['Navigator']);
+defineReplaceableLazyAttribute(globalThis, 'internal/navigator', ['navigator'], false);
+
 // https://w3c.github.io/FileAPI/#creating-revoking
 const { installObjectURLMethods } = require('internal/url');
 installObjectURLMethods();
diff --git a/lib/internal/navigator.js b/lib/internal/navigator.js
new file mode 100644
index 00000000000000..3b8343cd7ed6f8
--- /dev/null
+++ b/lib/internal/navigator.js
@@ -0,0 +1,49 @@
+'use strict';
+
+const {
+  ObjectDefineProperties,
+  Symbol,
+} = primordials;
+
+const {
+  ERR_ILLEGAL_CONSTRUCTOR,
+} = require('internal/errors').codes;
+
+const {
+  kEnumerableProperty,
+} = require('internal/util');
+
+const {
+  getAvailableParallelism,
+} = internalBinding('os');
+
+const kInitialize = Symbol('kInitialize');
+
+class Navigator {
+  // Private properties are used to avoid brand validations.
+  #availableParallelism;
+
+  constructor() {
+    if (arguments[0] === kInitialize) {
+      return;
+    }
+    throw ERR_ILLEGAL_CONSTRUCTOR();
+  }
+
+  /**
+   * @return {number}
+   */
+  get hardwareConcurrency() {
+    this.#availableParallelism ??= getAvailableParallelism();
+    return this.#availableParallelism;
+  }
+}
+
+ObjectDefineProperties(Navigator.prototype, {
+  hardwareConcurrency: kEnumerableProperty,
+});
+
+module.exports = {
+  navigator: new Navigator(kInitialize),
+  Navigator,
+};
diff --git a/test/common/index.js b/test/common/index.js
index 2a8ef3a3b183cc..c10dea59319264 100644
--- a/test/common/index.js
+++ b/test/common/index.js
@@ -309,6 +309,14 @@ if (global.gc) {
   knownGlobals.push(global.gc);
 }
 
+if (global.navigator) {
+  knownGlobals.push(global.navigator);
+}
+
+if (global.Navigator) {
+  knownGlobals.push(global.Navigator);
+}
+
 if (global.Performance) {
   knownGlobals.push(global.Performance);
 }
diff --git a/test/parallel/test-global.js b/test/parallel/test-global.js
index 9ac9b4f7287327..0a8fecd240c8a1 100644
--- a/test/parallel/test-global.js
+++ b/test/parallel/test-global.js
@@ -58,6 +58,7 @@ builtinModules.forEach((moduleName) => {
     'structuredClone',
     'fetch',
     'crypto',
+    'navigator',
   ];
   assert.deepStrictEqual(new Set(Object.keys(global)), new Set(expected));
 }
diff --git a/test/parallel/test-navigator.js b/test/parallel/test-navigator.js
new file mode 100644
index 00000000000000..300446f2c5dcdc
--- /dev/null
+++ b/test/parallel/test-navigator.js
@@ -0,0 +1,15 @@
+'use strict';
+
+require('../common');
+const assert = require('assert');
+
+const is = {
+  number: (value, key) => {
+    assert(!Number.isNaN(value), `${key} should not be NaN`);
+    assert.strictEqual(typeof value, 'number');
+  },
+};
+
+is.number(+navigator.hardwareConcurrency, 'hardwareConcurrency');
+is.number(navigator.hardwareConcurrency, 'hardwareConcurrency');
+assert.ok(navigator.hardwareConcurrency > 0);