diff --git a/NEWS b/NEWS
index 12e26c35cf2c64a4da058cac387d966d4827bcb5..23e86eeb4b37cc5bbedd9c8c3bda05e772e0ae0c 100644
--- a/NEWS
+++ b/NEWS
@@ -1,6 +1,15 @@
 News
 ====
 
+Version 2.4.0 rc1
+-----------------
+
+*(unreleased)*
+
+- Moved ``rapi_users`` file into separate directory, now named
+  ``…/ganeti/rapi/users``
+
+
 Version 2.3.0 rc1
 -----------------
 
diff --git a/daemons/ensure-dirs.in b/daemons/ensure-dirs.in
index 252f24af8fc877e3ba466fc8a4720a96a67fa6ca..272d40755fb4191b1bb64aee3bb3744d42e7a680 100644
--- a/daemons/ensure-dirs.in
+++ b/daemons/ensure-dirs.in
@@ -80,6 +80,7 @@ _ensure_datadir() {
   _ensure_dir ${DATADIR}/queue 0700 "$(_fileset_owner masterd)"
   _ensure_dir ${DATADIR}/queue/archive 0700 "$(_fileset_owner masterd)"
   _ensure_dir ${DATADIR}/uidpool 0750 "$(_fileset_owner noded)"
+  _ensure_dir ${DATADIR}/rapi 0750 "$(_fileset_owner rapi)"
 
   # We ignore these files if they don't exists (incomplete setup)
   _ensure_file ${DATADIR}/cluster-domain-secret 0640 \
@@ -88,7 +89,7 @@ _ensure_datadir() {
   _ensure_file ${DATADIR}/hmac.key 0440 "$(_fileset_owner confd)" || :
   _ensure_file ${DATADIR}/known_hosts 0644 "$(_fileset_owner masterd)" || :
   _ensure_file ${DATADIR}/rapi.pem 0440 "$(_fileset_owner rapi)" || :
-  _ensure_file ${DATADIR}/rapi_users 0640 "$(_fileset_owner rapi)" || :
+  _ensure_file ${DATADIR}/rapi/users 0640 "$(_fileset_owner rapi)" || :
   _ensure_file ${DATADIR}/server.pem 0440 "$(_fileset_owner masterd)" || :
   _ensure_file ${DATADIR}/queue/serial 0600 "$(_fileset_owner masterd)" || :
 
diff --git a/doc/rapi.rst b/doc/rapi.rst
index 9b53a8854bd5f90aec8c53856ceb0220b0a44920..f340c12fc3fb7103eea02aa2e98ef2ae04e60d0f 100644
--- a/doc/rapi.rst
+++ b/doc/rapi.rst
@@ -21,7 +21,7 @@ Users and passwords
 -------------------
 
 ``ganeti-rapi`` reads users and passwords from a file (usually
-``/var/lib/ganeti/rapi_users``) on startup. Changes to the file will be
+``/var/lib/ganeti/rapi/users``) on startup. Changes to the file will be
 read automatically.
 
 Each line consists of two or three fields separated by whitespace. The
diff --git a/lib/constants.py b/lib/constants.py
index 1222bf9faaf1a89940857b0b08c34c8ffdff90a6..cf04996301dd429fec1dd2abe5a3fc9d4b9fcdc9 100644
--- a/lib/constants.py
+++ b/lib/constants.py
@@ -135,7 +135,7 @@ WATCHER_STATEFILE = DATA_DIR + "/watcher.data"
 WATCHER_PAUSEFILE = DATA_DIR + "/watcher.pause"
 INSTANCE_UPFILE = RUN_GANETI_DIR + "/instance-status"
 SSH_KNOWN_HOSTS_FILE = DATA_DIR + "/known_hosts"
-RAPI_USERS_FILE = DATA_DIR + "/rapi_users"
+RAPI_USERS_FILE = DATA_DIR + "/rapi/users"
 QUEUE_DIR = DATA_DIR + "/queue"
 DAEMON_UTIL = _autoconf.PKGLIBDIR + "/daemon-util"
 SETUP_SSH = _autoconf.TOOLSDIR + "/setup-ssh"
diff --git a/man/ganeti-rapi.rst b/man/ganeti-rapi.rst
index d6e83ab1ed7c7242248dc0317043eea51a0ca8b1..2e5e3b4114c3b2531071ca285fbc4a58dab8bfe9 100644
--- a/man/ganeti-rapi.rst
+++ b/man/ganeti-rapi.rst
@@ -38,8 +38,8 @@ All query operations are allowed without authentication. Only the
 modification operations require authentication, in the form of basic
 authentication.
 
-The users and their rights are defined in a file named rapi_users,
-located in the ``@LOCALSTATEDIR@/lib/ganeti`` directory. The users
+The users and their rights are defined in the
+``@LOCALSTATEDIR@/lib/ganeti/rapi/users`` file. The users
 should be listed one per line, in the following format::
 
     username password options
diff --git a/test/cfgupgrade_unittest.py b/test/cfgupgrade_unittest.py
index 129be6f2c583fbd73ec2cee9d4fbfdb96dbc71d0..06a6f2cd5de977ab7a0c8756ec3b79422870a77a 100755
--- a/test/cfgupgrade_unittest.py
+++ b/test/cfgupgrade_unittest.py
@@ -57,6 +57,8 @@ class TestCfgupgrade(unittest.TestCase):
     self.config_path = utils.PathJoin(self.tmpdir, "config.data")
     self.noded_cert_path = utils.PathJoin(self.tmpdir, "server.pem")
     self.rapi_cert_path = utils.PathJoin(self.tmpdir, "rapi.pem")
+    self.rapi_users_path = utils.PathJoin(self.tmpdir, "rapi", "users")
+    self.rapi_users_path_pre24 = utils.PathJoin(self.tmpdir, "rapi_users")
     self.known_hosts_path = utils.PathJoin(self.tmpdir, "known_hosts")
     self.confd_hmac_path = utils.PathJoin(self.tmpdir, "hmac.key")
     self.cds_path = utils.PathJoin(self.tmpdir, "cluster-domain-secret")
@@ -122,12 +124,57 @@ class TestCfgupgrade(unittest.TestCase):
     newcfg = self._LoadConfig()
     self.assertEqual(newcfg["version"], expversion)
 
+  def testRapiUsers(self):
+    self.assertFalse(os.path.exists(self.rapi_users_path))
+    self.assertFalse(os.path.exists(self.rapi_users_path_pre24))
+
+    utils.WriteFile(self.rapi_users_path_pre24, data="some user\n")
+    self._TestSimpleUpgrade(constants.BuildVersion(2, 3, 0), False)
+
+    self.assert_(os.path.islink(self.rapi_users_path_pre24))
+    self.assert_(os.path.isfile(self.rapi_users_path))
+    for path in [self.rapi_users_path, self.rapi_users_path_pre24]:
+      self.assertEqual(utils.ReadFile(path), "some user\n")
+
+  def testRapiUsers24AndAbove(self):
+    self.assertFalse(os.path.exists(self.rapi_users_path))
+    self.assertFalse(os.path.exists(self.rapi_users_path_pre24))
+
+    os.mkdir(os.path.dirname(self.rapi_users_path))
+    utils.WriteFile(self.rapi_users_path, data="other user\n")
+    self._TestSimpleUpgrade(constants.BuildVersion(2, 3, 0), False)
+
+    self.assert_(os.path.islink(self.rapi_users_path_pre24))
+    self.assert_(os.path.isfile(self.rapi_users_path))
+    for path in [self.rapi_users_path, self.rapi_users_path_pre24]:
+      self.assertEqual(utils.ReadFile(path), "other user\n")
+
+  def testRapiUsersExistingSymlink(self):
+    self.assertFalse(os.path.exists(self.rapi_users_path))
+    self.assertFalse(os.path.exists(self.rapi_users_path_pre24))
+
+    os.symlink(self.rapi_users_path, self.rapi_users_path_pre24)
+    utils.WriteFile(self.rapi_users_path_pre24, data="hello world\n")
+
+    self._TestSimpleUpgrade(constants.BuildVersion(2, 2, 0), False)
+
+    self.assert_(os.path.isfile(self.rapi_users_path))
+    self.assert_(os.path.islink(self.rapi_users_path_pre24))
+    for path in [self.rapi_users_path, self.rapi_users_path_pre24]:
+      self.assertEqual(utils.ReadFile(path), "hello world\n")
+
   def testUpgradeFrom_2_0(self):
     self._TestSimpleUpgrade(constants.BuildVersion(2, 0, 0), False)
 
   def testUpgradeFrom_2_1(self):
     self._TestSimpleUpgrade(constants.BuildVersion(2, 1, 0), False)
 
+  def testUpgradeFrom_2_2(self):
+    self._TestSimpleUpgrade(constants.BuildVersion(2, 2, 0), False)
+
+  def testUpgradeFrom_2_3(self):
+    self._TestSimpleUpgrade(constants.BuildVersion(2, 3, 0), False)
+
   def testUpgradeCurrent(self):
     self._TestSimpleUpgrade(constants.CONFIG_VERSION, False)
 
@@ -137,6 +184,12 @@ class TestCfgupgrade(unittest.TestCase):
   def testUpgradeDryRunFrom_2_1(self):
     self._TestSimpleUpgrade(constants.BuildVersion(2, 1, 0), True)
 
+  def testUpgradeDryRunFrom_2_2(self):
+    self._TestSimpleUpgrade(constants.BuildVersion(2, 2, 0), True)
+
+  def testUpgradeDryRunFrom_2_3(self):
+    self._TestSimpleUpgrade(constants.BuildVersion(2, 3, 0), True)
+
   def testUpgradeCurrentDryRun(self):
     self._TestSimpleUpgrade(constants.CONFIG_VERSION, True)
 
diff --git a/tools/cfgupgrade b/tools/cfgupgrade
index 0b135ce2729618661eb4d8dee9e8bb48b8ef6663..4c10413d16d7350c7457705bce4950d8fa4a49b1 100755
--- a/tools/cfgupgrade
+++ b/tools/cfgupgrade
@@ -102,6 +102,8 @@ def main():
   options.SERVER_PEM_PATH = options.data_dir + "/server.pem"
   options.KNOWN_HOSTS_PATH = options.data_dir + "/known_hosts"
   options.RAPI_CERT_FILE = options.data_dir + "/rapi.pem"
+  options.RAPI_USERS_FILE = options.data_dir + "/rapi/users"
+  options.RAPI_USERS_FILE_PRE24 = options.data_dir + "/rapi_users"
   options.CONFD_HMAC_KEY = options.data_dir + "/hmac.key"
   options.CDS_FILE = options.data_dir + "/cluster-domain-secret"
 
@@ -155,6 +157,18 @@ def main():
     raise Error("Configuration version %d.%d.%d not supported by this tool" %
                 (config_major, config_minor, config_revision))
 
+  if os.path.isfile(options.RAPI_USERS_FILE_PRE24):
+    logging.info("Found pre-2.4 RAPI users file at %s, renaming to %s",
+                 options.RAPI_USERS_FILE_PRE24, options.RAPI_USERS_FILE)
+    utils.RenameFile(options.RAPI_USERS_FILE_PRE24, options.RAPI_USERS_FILE,
+                     mkdir=True, mkdir_mode=0750)
+
+  # Create a symlink for RAPI users file
+  if not os.path.islink(options.RAPI_USERS_FILE_PRE24):
+    logging.info("Creating symlink from %s to %s",
+                 options.RAPI_USERS_FILE_PRE24, options.RAPI_USERS_FILE)
+    os.symlink(options.RAPI_USERS_FILE, options.RAPI_USERS_FILE_PRE24)
+
   try:
     logging.info("Writing configuration file to %s", options.CONFIG_DATA_PATH)
     utils.WriteFile(file_name=options.CONFIG_DATA_PATH,