diff --git a/xroot_plugins/XrdSsiCtaServiceProvider.cpp b/xroot_plugins/XrdSsiCtaServiceProvider.cpp
index 5b9025c60e12ba07efc86c4d15bf950c9137ff92..ee42e84d96901bc9fc3a552b583f27bd0b5395d3 100644
--- a/xroot_plugins/XrdSsiCtaServiceProvider.cpp
+++ b/xroot_plugins/XrdSsiCtaServiceProvider.cpp
@@ -101,7 +101,7 @@ void XrdSsiCtaServiceProvider::ExceptionThrowingInit(XrdSsiLogger *logP, XrdSsiC
       } else if (loggerURL.second.substr(0, 5) == "file:") {
          m_log.reset(new log::FileLogger(shortHostname, "cta-frontend", loggerURL.second.substr(5), loggerLevel));
       } else {
-         throw exception::Exception(std::string("Unknown log URL: ") + loggerURL.second);
+         throw exception::UserError(std::string("Unknown log URL: ") + loggerURL.second);
       }
    } catch(exception::Exception &ex) {
       std::string ex_str("Failed to instantiate object representing CTA logging system: ");
@@ -115,7 +115,7 @@ void XrdSsiCtaServiceProvider::ExceptionThrowingInit(XrdSsiLogger *logP, XrdSsiC
    const rdbms::Login catalogueLogin = rdbms::Login::parseFile("/etc/cta/cta-catalogue.conf");
    auto catalogue_numberofconnections = config.getOptionValueInt("cta.catalogue.numberofconnections");
    if(!catalogue_numberofconnections.first) {
-      throw exception::Exception("cta.catalogue.numberofconnections is not set in configuration file " + cfgFn);
+      throw exception::UserError("cta.catalogue.numberofconnections is not set in configuration file " + cfgFn);
    }
    const uint64_t nbArchiveFileListingConns = 2;
 
@@ -129,7 +129,7 @@ void XrdSsiCtaServiceProvider::ExceptionThrowingInit(XrdSsiLogger *logP, XrdSsiC
    // Initialise the Backend
    auto objectstore_backendpath = config.getOptionValueStr("cta.objectstore.backendpath");
    if(!objectstore_backendpath.first) {
-      throw exception::Exception("cta.objectstore.backendpath is not set in configuration file " + cfgFn);
+      throw exception::UserError("cta.objectstore.backendpath is not set in configuration file " + cfgFn);
    }
    m_backend = std::move(cta::objectstore::BackendFactory::createBackend(objectstore_backendpath.second, *m_log));
    m_backendPopulator = cta::make_unique<cta::objectstore::BackendPopulator>(*m_backend, "Frontend", cta::log::LogContext(*m_log));
@@ -156,11 +156,12 @@ void XrdSsiCtaServiceProvider::ExceptionThrowingInit(XrdSsiLogger *logP, XrdSsiC
    }
 
    // Get the endpoint for namespace queries
-   auto nsEndpointConf = config.getOptionValueStr("cta.ns.endpoint");
-   //m_nsEndpoint = nsEndpointConf.first ? nsEndpointConf.second : "";
-   auto nsTokenConf = config.getOptionValueStr("cta.ns.token");
-   //m_nsToken = nsTokenConf.first ? nsTokenConf.second : "";
-   m_namespaceMap.insert(std::make_pair("endpoint", Namespace(nsEndpointConf.second, nsTokenConf.second)));
+   auto nsConf = config.getOptionValueStr("cta.ns.config");
+   if(nsConf.first) {
+      setNamespaceMap(nsConf.second);
+   } else {
+      Log::Msg(XrdSsiPb::Log::WARNING, LOG_SUFFIX, "warning: 'cta.ns.config' not specified; namespace queries are disabled");
+   }
   
    // Start the heartbeat thread for the agent object. The thread is guaranteed to have started before we call the unique_ptr deleter
    auto aht = new cta::objectstore::AgentHeartbeatThread(m_backendPopulator->getAgentReference(), *m_backend, *m_log);
@@ -171,6 +172,40 @@ void XrdSsiCtaServiceProvider::ExceptionThrowingInit(XrdSsiLogger *logP, XrdSsiC
    log(log::INFO, std::string("cta-frontend started"), params);
 }
 
+void XrdSsiCtaServiceProvider::setNamespaceMap(const std::string &keytab_file)
+{
+   // Open the keytab file for reading
+   std::ifstream file(keytab_file);
+   if(!file) {
+      throw cta::exception::UserError("Failed to open namespace keytab configuration file " + keytab_file);
+   }
+
+   // Parse the keytab line by line
+   std::string line;
+   for(int lineno = 0; std::getline(file, line); ++lineno) {
+      // Strip out comments
+      auto pos = line.find('#');
+      if(pos != std::string::npos) {
+         line.resize(pos);
+      }
+
+      // Parse one line
+      std::stringstream ss(line);
+      std::string diskInstance;
+      std::string endpoint;
+      std::string token;
+      std::string eol;
+      ss >> diskInstance >> endpoint >> token >> eol;
+
+      // Ignore blank lines, all other lines must have exactly 3 elements
+      if(token.empty() || !eol.empty()) {
+         if(diskInstance.empty() && endpoint.empty() && token.empty()) continue;
+         throw cta::exception::UserError("Could not parse namespace keytab configuration file line " + std::to_string(lineno) + ": " + line);
+      }
+      m_namespaceMap.insert(std::make_pair(diskInstance, cta::Namespace(endpoint, token)));
+   }
+}
+
 XrdSsiService* XrdSsiCtaServiceProvider::GetService(XrdSsiErrInfo &eInfo, const std::string &contact, int oHold)
 {
    XrdSsiPb::Log::Msg(XrdSsiPb::Log::INFO, LOG_SUFFIX, "Called GetService(", contact, ',', oHold, ')');
diff --git a/xroot_plugins/XrdSsiCtaServiceProvider.hpp b/xroot_plugins/XrdSsiCtaServiceProvider.hpp
index babdd1069ea30058621d204b3c63f9b471c51e80..74acb3ce53d0e2873874bdb0a7a042b175c55c5d 100644
--- a/xroot_plugins/XrdSsiCtaServiceProvider.hpp
+++ b/xroot_plugins/XrdSsiCtaServiceProvider.hpp
@@ -110,6 +110,11 @@ public:
     */
    cta::optional<std::string> getRepackBufferURL() const { return m_repackBufferURL; }
 
+   /*!
+    * Populate the namespace endpoint configuration from a keytab file
+    */
+   void setNamespaceMap(const std::string &keytab_file);
+
    /*!
     * Get the endpoints for namespace queries
     */
diff --git a/xroot_plugins/cta-frontend-xrootd.conf b/xroot_plugins/cta-frontend-xrootd.conf
index b0585903132e1daad5264ec13e1823d63deca515..e047c265a661cc771fd218b82e4a5802181a59b2 100644
--- a/xroot_plugins/cta-frontend-xrootd.conf
+++ b/xroot_plugins/cta-frontend-xrootd.conf
@@ -14,12 +14,20 @@ cta.catalogue.numberofconnections 10
 # CTA Frontend log URL
 cta.log.url file:/var/log/cta/cta-frontend.log
 
-# CTA XRootD SSI/Protobuf log level
+# CTA Logger log level
+# Valid log levels are EMERG, ALERT, CRIT, ERR, WARNING, NOTICE (==USERERR), INFO, DEBUG
+# cta.log.level DEBUG
+
+# CTA XRootD SSI Protobuf log level
+# cta.log.ssi debug protobuf
 cta.log.ssi warning
 
 # CTA Repack buffer URL
 # cta.repack.repack_buffer_url root://ctaeos//eos/ctaeos/repack
 
+# Keytab containing gRPC endpoints and tokens for each disk instance
+cta.ns.config /etc/cta/eos.grpc.keytab
+
 #
 # XRootD/SSI options
 #