diff --git a/nss/Makefile b/nss/Makefile
index 1c48bd0876..42d28cf40a 100644
--- a/nss/Makefile
+++ b/nss/Makefile
@@ -324,6 +324,7 @@ tests := \
   tst-gethnm \
   tst-getpw \
   tst-gshadow \
+  tst-nss-does-not-exist \
   tst-nss-getpwent \
   tst-nss-hash \
   tst-nss-malloc-failure-getlogin_r \
@@ -537,3 +538,5 @@ LDFLAGS-tst-nss-test4 = -Wl,--disable-new-dtags
 LDFLAGS-tst-nss-test5 = -Wl,--disable-new-dtags
 LDFLAGS-tst-nss-test_errno = -Wl,--disable-new-dtags
 LDFLAGS-tst-nss-test_gai_hv2_canonname = -Wl,--disable-new-dtags
+
+LDLIBS-tst-nss-does-not-exist = -lstdc++
diff --git a/nss/tst-nss-does-not-exist.cc b/nss/tst-nss-does-not-exist.cc
new file mode 100644
index 0000000000..72f7c635d8
--- /dev/null
+++ b/nss/tst-nss-does-not-exist.cc
@@ -0,0 +1,210 @@
+/* Verify that lookup failures have the expected error behavior.
+   Copyright (C) 2026 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+/* This test case triggers lookup failures for most NSS functions
+   (both allocated and *_r variants).  It is written in C++ so that
+   the required types can be inferred from the lookup functions
+   themselves.  */
+
+#include <aliases.h>
+#include <grp.h>
+#include <netdb.h>
+#include <pwd.h>
+#include <netinet/ether.h>
+
+#include <errno.h>
+#include <nss.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <support/check.h>
+#include <support/namespace.h>
+#include <support/test-driver.h>
+
+#include <utility>
+#include <vector>
+
+static const char non_existing_name[]
+  = "this-name-does-not-exist-in-the-system";
+
+/* True if *_r lookup functions for type T use an extra h_errno
+   argument.  */
+template <class T> constexpr bool uses_h_errno =
+  std::is_same_v <T, hostent> || std::is_same_v <T, netent>;
+
+/* Call a *_r NSS function that produces an NSS lookup of result T
+   repeatedly until it does not return ERANGE.  The final return value
+   is written to the ret member, and the pointer to the result
+   structure to the result member.  The result can be nullptr for a
+   negative lookup result.  */
+template <class T>
+struct erange_wrapper
+{
+  int ret;
+  T *result;
+
+  template <class Impl, class... Args>
+  erange_wrapper (Impl impl, Args&&... args)
+  {
+    buffer.resize (32);
+    result = (T *) -1;
+    while (true)
+      {
+        h_errno = 0;
+        int herrno = 0;
+        if constexpr (uses_h_errno <T>)
+          ret = impl (std::forward<Args>(args)...,
+                      &storage, buffer.data (), buffer.size (), &result,
+                      &herrno);
+        else
+          {
+            /* No h_errno pointer argument.  Pretend that failures
+               are always due to NETDB_INTERNAL.  */
+            ret = impl (std::forward<Args>(args)...,
+                        &storage, buffer.data (), buffer.size (), &result);
+            herrno = NETDB_INTERNAL;
+          }
+        if (herrno == NETDB_INTERNAL && ret == ERANGE)
+          {
+            buffer.resize (buffer.size () * 2);
+            TEST_VERIFY (buffer.size () > 0);
+          }
+        else
+          break;
+      }
+  }
+
+  /* Verify that ret and result denote a negative lookup result.  */
+  void expected_negative ()
+  {
+    TEST_COMPARE (ret, 0);
+    TEST_VERIFY (result == nullptr);
+  }
+
+private:
+  T storage;
+  std::vector<char> buffer;
+};
+
+/* Verify that looking up non_existing_name fails for the non-_r and _r
+   function variants.  */
+template <class Lookup, class LookupR, class... Args>
+static void
+check_missing_name (Lookup lookup, LookupR lookup_r, Args&&... args)
+{
+  verbose_printf ("info: name lookup: %s\n",
+                  strchr (__PRETTY_FUNCTION__, '['));
+  errno = 0;
+  auto r = lookup (non_existing_name, std::forward<Args>(args)...);
+  TEST_VERIFY (r == nullptr);
+  TEST_COMPARE (errno, 0);
+  using nss_type = typename std::remove_reference <decltype (*r)>::type;
+  erange_wrapper <nss_type> (lookup_r, non_existing_name,
+                             std::forward<Args>(args)...).expected_negative ();
+}
+
+/* Find an ID that results in a lookup failure using the non-_r function,
+   and then verify that it also fails with the _r function.  ID_MEMBER
+   is the pointer to the struct member that contains the looked-up ID.  */
+template <class Lookup, class LookupR, class Member, class... Args>
+static void
+find_missing_id (const char *type_name, Lookup lookup, LookupR lookup_r,
+                 Member id_member, Args&&... args)
+{
+  verbose_printf ("info: ID lookup for %s: %s\n",
+                  type_name, strchr (__PRETTY_FUNCTION__, '['));
+  using nss_type = typename std::remove_reference
+    <decltype (*lookup (0, std::forward<Args>(args)...))>::type;
+  using id_type = decltype(nss_type{}.*id_member);
+  for (id_type i = 0; i < 65536; ++i)
+    {
+      verbose_printf ("info:   trying %lld\n", (long long int) i);
+      errno = 0;
+      nss_type *r = lookup (i, std::forward<Args>(args)...);
+      if (r == nullptr)
+        {
+          TEST_COMPARE (errno, 0);
+          printf ("info: first missing %s: %lld\n", type_name,
+                  (long long int) i);
+
+          erange_wrapper <nss_type> (lookup_r, i, std::forward<Args>(args)...)
+            .expected_negative ();
+          return;
+        }
+      TEST_COMPARE (r->*id_member, i);
+    }
+  printf ("warning: could not find an undefined ID for %s\n", type_name);
+}
+
+static void
+checks (void *closure)
+{
+  check_missing_name (getpwnam, getpwnam_r);
+  find_missing_id ("passwd", getpwuid, getpwuid_r, &passwd::pw_uid);
+
+  check_missing_name (getgrnam, getgrnam_r);
+  find_missing_id ("group", getgrgid, getgrgid_r, &group::gr_gid);
+
+  check_missing_name (getprotobyname, getprotobyname_r);
+  find_missing_id ("protoent", getprotobynumber, getprotobynumber_r,
+                   &protoent::p_proto);
+
+  check_missing_name (getservbyname, getservbyname_r, "tcp");
+  find_missing_id ("servent", getservbyport, getservbyport_r,
+                   &servent::s_port, "tcp");
+
+  check_missing_name (getaliasbyname, getaliasbyname_r);
+
+  check_missing_name (getrpcbyname, getrpcbyname_r);
+  find_missing_id ("rpcent", getrpcbynumber, getrpcbynumber_r,
+                   &rpcent::r_number);
+
+  check_missing_name (gethostbyname, gethostbyname_r);
+  check_missing_name (gethostbyname2, gethostbyname2_r, AF_INET);
+
+  check_missing_name (getnetbyname, getnetbyname_r);
+
+  /* Exceptional case: no buffer argument, no ERANGE protocol.  */
+  {
+    errno = 0;
+    TEST_VERIFY (ether_aton (non_existing_name) == nullptr);
+    TEST_COMPARE (errno, 0);
+    struct ether_addr addr;
+    TEST_VERIFY (ether_aton_r (non_existing_name, &addr) == nullptr);
+    TEST_COMPARE (errno, 0);
+  }
+}
+
+static int
+do_test (void)
+{
+  /* First test the system default.  */
+  puts ("info: testing system default");
+  support_isolate_in_subprocess (checks, nullptr);
+
+  /* Then test the files module specifically.  */
+  puts ("info: testing files module");
+#define DEFINE_DATABASE(db) __nss_configure_lookup (#db, "files");
+#include "databases.def"
+#undef DEFINE_DATABASE
+  support_isolate_in_subprocess (checks, nullptr);
+
+  return 0;
+}
+
+#include <support/test-driver.c>
