diff --git a/doc/api/errors.md b/doc/api/errors.md
index 5a08358f2c..6ea6362c2f 100644
--- a/doc/api/errors.md
+++ b/doc/api/errors.md
@@ -1695,6 +1695,13 @@ TBD
TBD
+
+### `ERR_QUIC_UNAVAILABLE`
+
+> Stabililty: 1 - Experimental
+
+TBD
+
### `ERR_QUICCLIENTSESSION_FAILED`
diff --git a/lib/internal/errors.js b/lib/internal/errors.js
index e7a371b0e1..858d1849e3 100644
--- a/lib/internal/errors.js
+++ b/lib/internal/errors.js
@@ -1335,6 +1335,7 @@ E('ERR_QUIC_ERROR', function(code, family) {
return `QUIC session closed with ${familyType} error code ${code}`;
}, Error);
E('ERR_QUIC_TLS13_REQUIRED', 'QUIC requires TLS version 1.3', Error);
+E('ERR_QUIC_UNAVAILABLE', 'QUIC is unavailable', Error);
E('ERR_REQUIRE_ESM',
(filename, parentPath = null, packageJsonPath = null) => {
let msg = `Must use import to load ES Module: ${filename}`;
diff --git a/lib/internal/quic/core.js b/lib/internal/quic/core.js
index 18f60820c5..e906fb6b23 100644
--- a/lib/internal/quic/core.js
+++ b/lib/internal/quic/core.js
@@ -245,6 +245,8 @@ const kSocketDestroyed = 4;
let diagnosticPacketLossWarned = false;
let warnedVerifyHostnameIdentity = false;
+assert(process.versions.ngtcp2 !== undefined);
+
// Called by the C++ internals when the socket is closed.
// When this is called, the only thing left to do is destroy
// the QuicSocket instance.
diff --git a/lib/internal/quic/util.js b/lib/internal/quic/util.js
index 28119b8e26..d6e5dc1069 100644
--- a/lib/internal/quic/util.js
+++ b/lib/internal/quic/util.js
@@ -9,6 +9,9 @@ const {
},
} = require('internal/errors');
+const assert = require('internal/assert');
+assert(process.versions.ngtcp2 !== undefined);
+
const {
isIP,
} = require('internal/net');
diff --git a/lib/quic.js b/lib/quic.js
index ca96385abf..4a9f05665d 100644
--- a/lib/quic.js
+++ b/lib/quic.js
@@ -1,5 +1,16 @@
'use strict';
+const {
+ codes: {
+ ERR_QUIC_UNAVAILABLE,
+ },
+} = require('internal/errors');
+const { assertCrypto } = require('internal/util');
+
+assertCrypto();
+if (process.versions.ngtcp2 === undefined)
+ throw new ERR_QUIC_UNAVAILABLE();
+
const {
createSocket
} = require('internal/quic/core');
diff --git a/src/node_native_module.cc b/src/node_native_module.cc
index 4ffc666600..786b9cc154 100644
--- a/src/node_native_module.cc
+++ b/src/node_native_module.cc
@@ -87,23 +87,20 @@ void NativeModuleLoader::InitializeModuleCategories() {
"crypto",
"https",
"http2",
-#if !defined(NODE_EXPERIMENTAL_QUIC)
- "quic",
-#endif
"tls",
"_tls_common",
"_tls_wrap",
"internal/http2/core",
"internal/http2/compat",
-#if !defined(NODE_EXPERIMENTAL_QUIC)
- "internal/quic/core",
- "internal/quic/util",
-#endif
"internal/policy/manifest",
"internal/process/policy",
"internal/streams/lazy_transform",
#endif // !HAVE_OPENSSL
-
+#if !defined(NODE_EXPERIMENTAL_QUIC)
+ "quic",
+ "internal/quic/core",
+ "internal/quic/util",
+#endif
"sys", // Deprecated.
"wasi", // Experimental.
"internal/test/binding",