diff --git a/lib/bb/cookerdata.py b/lib/bb/cookerdata.py
index d65e3c974..87310af7c 100644
--- a/lib/bb/cookerdata.py
+++ b/lib/bb/cookerdata.py
@@ -482,6 +482,18 @@ class CookerDataBuilder(object):
                         " invoked bitbake from the wrong directory?")
             bb.fatal(msg)
 
+        # A directory listed more than once in BBPATH is a configuration
+        # error, e.g. a layer in both the BBPATH env var and BBLAYERS. Compare
+        # resolved paths so "dir" and "dir/" or a symlink to it also match, and
+        # ignore the empty entries left by stray colons
+        entries = {}
+        for p in data.getVar("BBPATH").split(":"):
+            if p:
+                entries.setdefault(os.path.realpath(p), []).append(p)
+        duplicates = [" ".join(paths) for paths in entries.values() if len(paths) > 1]
+        if duplicates:
+            bb.fatal("BBPATH contains duplicate entries:\n    %s\nCheck the BBPATH environment variable, bblayers.conf and the layer.conf files." % "\n    ".join(duplicates))
+
         if not data.getVar("TOPDIR"):
             data.setVar("TOPDIR", os.path.abspath(os.getcwd()))
         if not data.getVar("BB_CACHEDIR"):
diff --git a/lib/bb/tests/parse.py b/lib/bb/tests/parse.py
index cd8f02388..90a63e7dc 100644
--- a/lib/bb/tests/parse.py
+++ b/lib/bb/tests/parse.py
@@ -531,6 +531,33 @@ EXTRA_OECONF:append = " foobar"
             self.assertEqual(d.getVar("EXTRA_OECONF"), " foo bar")
             self.assertEqual(logs.output, ["WARNING:BitBake.Parsing:parse done"])
 
+    def test_duplicate_bbpath(self):
+        # A directory listed more than once in BBPATH is fatal
+        bbpath = os.path.realpath(os.path.join(os.path.dirname(__file__), "parse-tests"))
+        with tempfile.TemporaryDirectory(prefix="parsetest") as builddir:
+            def run_bitbake(entries):
+                env = os.environ.copy()
+                env["BBPATH"] = ":".join(entries)
+                env["BB_ENV_PASSTHROUGH_ADDITIONS"] = "TOPDIR"
+                env["TOPDIR"] = builddir
+                return subprocess.run(["bitbake", "-e"], env=env, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True, cwd=builddir)
+
+            link = os.path.join(builddir, "link")
+            os.symlink(bbpath, link)
+
+            # The same directory spelled the same, with a trailing slash or
+            # through a symlink is a duplicate
+            for dup in (bbpath, bbpath + "/", link):
+                with self.subTest(dup=dup):
+                    result = run_bitbake([bbpath, dup])
+                    self.assertNotEqual(result.returncode, 0)
+                    self.assertIn("BBPATH contains duplicate entries:\n    %s %s\n" % (bbpath, dup), result.stdout)
+
+            # Empty entries from stray colons are not duplicates
+            result = run_bitbake(["", bbpath, "", ""])
+            self.assertEqual(result.returncode, 0, result.stdout)
+            self.assertNotIn("BBPATH contains duplicate entries", result.stdout)
+
     def test_file_variables(self):
         # Tests the values of FILE and __BB_RECIPE_FILE in different
         # combinations of bbappends, includes, and inherits
