diff --git a/files/gitlab-cookbooks/gitaly/libraries/gitaly.rb b/files/gitlab-cookbooks/gitaly/libraries/gitaly.rb
index 669191a70a82bd75789f98111a4890092d8576d0..a126968d6ec47a8fd7f965b972f6daac646d24bb 100644
--- a/files/gitlab-cookbooks/gitaly/libraries/gitaly.rb
+++ b/files/gitlab-cookbooks/gitaly/libraries/gitaly.rb
@@ -27,6 +27,7 @@ module Gitaly
       parse_git_data_dirs
       parse_gitaly_storages
       parse_gitconfig
+      check_duplicate_storage_paths
     end
 
     def gitaly_address
@@ -160,6 +161,32 @@ module Gitaly
       Chef::Mixin::DeepMerge.deep_merge!(tmp_source_hash, Gitlab['gitaly'])
     end
 
+    # Validate that no storages are sharing the same path.
+    def check_duplicate_storage_paths
+      # Deep copy storages to avoid mutating the original.
+      storages = Marshal.load(Marshal.dump(Gitlab['gitaly']['configuration']['storage']))
+
+      storages.each do |storage|
+        storage[:realpath] =
+          begin
+            File.realpath(storage[:path])
+          rescue Errno::ENOENT
+            storage[:path]
+          end
+      end
+
+      realpath_duplicates = storages.group_by { |storage| storage[:realpath] }.select { |_, entries| entries.size > 1 }
+
+      return if realpath_duplicates.empty?
+
+      output = realpath_duplicates.map do |realpath, entries|
+        names = entries.map { |s| s[:name] }.join(', ')
+        "#{realpath}: #{names}"
+      end
+
+      raise "One or more Gitaly storages are accessible by multiple filesystem paths:\n  #{output.join('\n  ')}"
+    end
+
     private
 
     def user_config
diff --git a/spec/chef/cookbooks/gitaly/recipes/gitaly_spec.rb b/spec/chef/cookbooks/gitaly/recipes/gitaly_spec.rb
index 13ec76272d1a80dd4e34c18b1533a9d1dc3d6c77..61d738128e6e940d29ee7a678d4e97f6b1ccf486 100644
--- a/spec/chef/cookbooks/gitaly/recipes/gitaly_spec.rb
+++ b/spec/chef/cookbooks/gitaly/recipes/gitaly_spec.rb
@@ -803,6 +803,48 @@ RSpec.describe 'gitaly' do
               .with_content(%r{\[\[storage\]\]\s+name = "nfs1"\s+path = "/mnt/nfs1/repositories"})
           end
         end
+
+        context 'with multiple storages using the same path' do
+          let(:real_path) { Dir.mktmpdir }
+          let(:other_dir) { Dir.mktmpdir }
+          let(:symlink_path) { File.join(other_dir, 'symlink') }
+
+          before do
+            File.symlink(real_path, symlink_path)
+            stub_gitlab_rb(
+              gitaly: {
+                configuration: {
+                  storage: [
+                    {
+                      'name' => 'default',
+                      'path' => '/var/opt/gitlab/git-data/repositories'
+                    },
+                    {
+                      'name' => 'other',
+                      'path' => '/var/opt/gitlab/git-data/repositories'
+                    },
+                    {
+                      'name' => 'realpath',
+                      'path' => real_path
+                    },
+                    {
+                      'name' => 'symlinked',
+                      'path' => symlink_path,
+                    }
+                  ]
+                }
+              }
+            )
+          end
+
+          after do
+            FileUtils.rm_rf([real_path, other_dir])
+          end
+
+          it 'raises an error' do
+            expect { chef_run }.to raise_error(/One or more Gitaly storages are accessible by multiple filesystem paths:.*: default.*: realpath/m)
+          end
+        end
       end
     end
   end