diff --git a/app/models/packages/nuget/symbol.rb b/app/models/packages/nuget/symbol.rb
index adcfaef4f4cdd50f06c1b2b505a04dda4418e84b..f15b045f303ffd2089ec44d8092fd666f01a517e 100644
--- a/app/models/packages/nuget/symbol.rb
+++ b/app/models/packages/nuget/symbol.rb
@@ -26,6 +26,9 @@ class Symbol < ApplicationRecord
 
       scope :stale, -> { where(package_id: nil) }
       scope :pending_destruction, -> { stale.default }
+      scope :with_file_name, ->(file_name) { where(file: file_name) }
+      scope :with_signature, ->(signature) { where(signature: signature) }
+      scope :with_file_sha256, ->(checksums) { where(file_sha256: checksums) }
 
       private
 
diff --git a/config/feature_flags/development/nuget_symbolfiles_endpoint.yml b/config/feature_flags/development/nuget_symbolfiles_endpoint.yml
new file mode 100644
index 0000000000000000000000000000000000000000..a8512c7190ae9f4434de7c56d4d6e0277bc35934
--- /dev/null
+++ b/config/feature_flags/development/nuget_symbolfiles_endpoint.yml
@@ -0,0 +1,8 @@
+---
+name: nuget_symbolfiles_endpoint
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/134564
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/433528
+milestone: '16.7'
+type: development
+group: group::package registry
+default_enabled: false
diff --git a/lib/api/concerns/packages/nuget/public_endpoints.rb b/lib/api/concerns/packages/nuget/public_endpoints.rb
index b0c9177f4526e72bbe7a1e15c0063b61f5d3420f..740ff97e20c5934bac5eee0c92bb8e7c11de20f3 100644
--- a/lib/api/concerns/packages/nuget/public_endpoints.rb
+++ b/lib/api/concerns/packages/nuget/public_endpoints.rb
@@ -14,6 +14,8 @@ module Nuget
         module PublicEndpoints
           extend ActiveSupport::Concern
 
+          SHA256_REGEX = /SHA256:([a-f0-9]{64})/i
+
           included do
             # https://docs.microsoft.com/en-us/nuget/api/service-index
             desc 'The NuGet V3 Feed Service Index' do
@@ -43,6 +45,56 @@ module PublicEndpoints
               ]
               tags %w[nuget_packages]
             end
+
+            namespace :symbolfiles do
+              after_validation do
+                not_found! if Feature.disabled?(:nuget_symbolfiles_endpoint, project_or_group_without_auth)
+              end
+
+              desc 'The NuGet Symbol File Download Endpoint' do
+                detail 'This feature was introduced in GitLab 16.7'
+                success code: 200
+                failure [
+                  { code: 400, message: 'Bad Request' },
+                  { code: 404, message: 'Not Found' }
+                ]
+                headers Symbolchecksum: {
+                  type: String,
+                  desc: 'The SHA256 checksums of the symbol file',
+                  required: true
+                }
+                tags %w[nuget_packages]
+              end
+              params do
+                requires :file_name, allow_blank: false, type: String, desc: 'The symbol file name',
+                  regexp: API::NO_SLASH_URL_PART_REGEX, documentation: { example: 'mynugetpkg.pdb' }
+                requires :signature, allow_blank: false, type: String, desc: 'The symbol file signature',
+                  regexp: API::NO_SLASH_URL_PART_REGEX,
+                  documentation: { example: 'k813f89485474661234z7109cve5709eFFFFFFFF' }
+                requires :same_file_name, same_as: :file_name
+              end
+              get '*file_name/*signature/*same_file_name', format: false, urgency: :low do
+                bad_request!('Missing checksum header') if headers['Symbolchecksum'].blank?
+
+                project_or_group_without_auth
+
+                # upcase the age part of the signature in case we received it in lowercase:
+                # https://github.com/dotnet/symstore/blob/main/docs/specs/SSQP_Key_Conventions.md#key-formatting-basic-rules
+                signature = declared_params[:signature].sub(/.{8}\z/, &:upcase)
+                checksums = headers['Symbolchecksum'].scan(SHA256_REGEX).flatten
+
+                symbol = ::Packages::Nuget::Symbol
+                  .with_signature(signature)
+                  .with_file_name(declared_params[:file_name])
+                  .with_file_sha256(checksums)
+                  .first
+
+                not_found!('Symbol') unless symbol
+
+                present_carrierwave_file!(symbol.file)
+              end
+            end
+
             namespace '/v2' do
               get format: :xml, urgency: :low do
                 env['api.format'] = :xml
diff --git a/spec/models/packages/nuget/symbol_spec.rb b/spec/models/packages/nuget/symbol_spec.rb
index 8d8604bb84a9a0efad45fcf602b97e94d6d1e7ea..bae8f90c7d50b060ea403848d8d9de7a81de4625 100644
--- a/spec/models/packages/nuget/symbol_spec.rb
+++ b/spec/models/packages/nuget/symbol_spec.rb
@@ -45,6 +45,43 @@
 
       it { is_expected.to contain_exactly(stale_symbol) }
     end
+
+    describe '.with_signature' do
+      subject(:with_signature) { described_class.with_signature(signature) }
+
+      let_it_be(:signature) { 'signature' }
+      let_it_be(:symbol) { create(:nuget_symbol, signature: signature) }
+
+      it 'returns symbols with the given signature' do
+        expect(with_signature).to eq([symbol])
+      end
+    end
+
+    describe '.with_file_name' do
+      subject(:with_file_name) { described_class.with_file_name(file_name) }
+
+      let_it_be(:file_name) { 'file_name' }
+      let_it_be(:symbol) { create(:nuget_symbol) }
+
+      before do
+        symbol.update_column(:file, file_name)
+      end
+
+      it 'returns symbols with the given file_name' do
+        expect(with_file_name).to eq([symbol])
+      end
+    end
+
+    describe '.with_file_sha256' do
+      subject(:with_file_sha256) { described_class.with_file_sha256(checksums) }
+
+      let_it_be(:checksums) { OpenSSL::Digest.hexdigest('SHA256', 'checksums') }
+      let_it_be(:symbol) { create(:nuget_symbol, file_sha256: checksums) }
+
+      it 'returns symbols with the given checksums' do
+        expect(with_file_sha256).to eq([symbol])
+      end
+    end
   end
 
   describe 'callbacks' do
diff --git a/spec/requests/api/nuget_group_packages_spec.rb b/spec/requests/api/nuget_group_packages_spec.rb
index 92eb869b871023415a67211fda738dc40690b447..4a763b3bbda7dc9a9a03233dbe6f7f38331533f8 100644
--- a/spec/requests/api/nuget_group_packages_spec.rb
+++ b/spec/requests/api/nuget_group_packages_spec.rb
@@ -188,6 +188,14 @@ def update_visibility_to(visibility)
       end
     end
 
+    describe 'GET /api/v4/groups/:id/-/packages/nuget/token/*token/symbolfiles/*file_name/*signature/*file_name' do
+      it_behaves_like 'nuget symbol file endpoint' do
+        let(:url) do
+          "/groups/#{target.id}/-/packages/nuget/symbolfiles/#{filename}/#{signature}/#{filename}"
+        end
+      end
+    end
+
     def update_visibility_to(visibility)
       project.update!(visibility_level: visibility)
       subgroup.update!(visibility_level: visibility)
diff --git a/spec/requests/api/nuget_project_packages_spec.rb b/spec/requests/api/nuget_project_packages_spec.rb
index a116be84b3ef965168c2dbf0f57d5880cfc472e4..8252fc1c4cdfa510c49288d40c977403caa76aa5 100644
--- a/spec/requests/api/nuget_project_packages_spec.rb
+++ b/spec/requests/api/nuget_project_packages_spec.rb
@@ -419,6 +419,12 @@ def snowplow_context(user_role: :developer, event_user: user)
     end
   end
 
+  describe 'GET /api/v4/projects/:id/packages/nuget/symbolfiles/*file_name/*signature/*file_name' do
+    it_behaves_like 'nuget symbol file endpoint' do
+      let(:url) { "/projects/#{target.id}/packages/nuget/symbolfiles/#{filename}/#{signature}/#{filename}" }
+    end
+  end
+
   def update_visibility_to(visibility)
     project.update!(visibility_level: visibility)
   end
diff --git a/spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb
index c23d514abfc4fcbb6858f97505964880bf15094e..df7e36202ec3c9cd6928e4f3085fd1e836366238 100644
--- a/spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb
@@ -741,3 +741,61 @@
     end
   end
 end
+
+RSpec.shared_examples 'nuget symbol file endpoint' do
+  let_it_be(:symbol) { create(:nuget_symbol) }
+  let_it_be(:filename) { symbol.file.filename }
+  let_it_be(:signature) { symbol.signature }
+  let_it_be(:checksum) { symbol.file_sha256.delete("\n") }
+
+  let(:headers) { { 'Symbolchecksum' => "SHA256:#{checksum}" } }
+
+  subject { get api(url), headers: headers }
+
+  it { is_expected.to have_request_urgency(:low) }
+
+  context 'with valid target' do
+    it 'returns the symbol file' do
+      subject
+
+      expect(response).to have_gitlab_http_status(:ok)
+      expect(response.media_type).to eq('application/octet-stream')
+      expect(response.body).to eq(symbol.file.read)
+    end
+  end
+
+  context 'when nuget_symbolfiles_endpoint feature flag is disabled' do
+    before do
+      stub_feature_flags(nuget_symbolfiles_endpoint: false)
+    end
+
+    it_behaves_like 'returning response status', :not_found
+  end
+
+  context 'when target does not exist' do
+    let(:target) { double(id: 1234567890) }
+
+    it_behaves_like 'returning response status', :not_found
+  end
+
+  context 'when target exists' do
+    context 'when symbol file does not exist' do
+      let(:filename) { 'non-existent-file.pdb' }
+      let(:signature) { 'non-existent-signature' }
+
+      it_behaves_like 'returning response status', :not_found
+    end
+
+    context 'when symbol file checksum does not match' do
+      let(:checksum) { 'non-matching-checksum' }
+
+      it_behaves_like 'returning response status', :not_found
+    end
+
+    context 'when symbol file checksum is missing' do
+      let(:headers) { {} }
+
+      it_behaves_like 'returning response status', :bad_request
+    end
+  end
+end