diff --git a/doc/administration/packages/index.md b/doc/administration/packages/index.md
index f33c215fdb8b80207520e9d33923c96c1316c296..536b6a5f246cfbca1f016ce26fb286351e77c8a0 100644
--- a/doc/administration/packages/index.md
+++ b/doc/administration/packages/index.md
@@ -119,9 +119,6 @@ upload packages:
    }
    ```
 
-   NOTE: **Note:**
-   Some build tools, like Gradle, must make `HEAD` requests to Amazon S3 to pull a dependency’s metadata. The `gitlab_rails['packages_object_store_proxy_download']` property must be set to `true`. Without this setting, GitLab won't act as a proxy to the Amazon S3 service, and will instead return the signed URL. This will cause a `HTTP 403 Forbidden` response, since Amazon S3 expects a signed URL.
-
 1. Save the file and [reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure)
    for the changes to take effect.
 
diff --git a/ee/changelogs/unreleased/32102-fix-maven-head-redirects.yml b/ee/changelogs/unreleased/32102-fix-maven-head-redirects.yml
new file mode 100644
index 0000000000000000000000000000000000000000..edb6a77707cdf6c83b20b139d527dd9a71e70a0f
--- /dev/null
+++ b/ee/changelogs/unreleased/32102-fix-maven-head-redirects.yml
@@ -0,0 +1,6 @@
+---
+title: Maven packages API allows HEAD requests to package files when using Amazon
+  S3 as a object storage backend
+merge_request: 27612
+author:
+type: fixed
diff --git a/ee/lib/api/maven_packages.rb b/ee/lib/api/maven_packages.rb
index 21aaa2d8cf402d879e72f118b1309b850225e727..07d9487e95751c6410cbb04cf98136433f0aab20 100644
--- a/ee/lib/api/maven_packages.rb
+++ b/ee/lib/api/maven_packages.rb
@@ -50,6 +50,31 @@ def find_project_by_path(path)
       def jar_file?(format)
         format == 'jar'
       end
+
+      def present_carrierwave_file_with_head_support!(file, supports_direct_download: true)
+        if head_request_on_aws_file?(file, supports_direct_download) && !file.file_storage?
+          return redirect(signed_head_url(file))
+        end
+
+        present_carrierwave_file!(file, supports_direct_download: supports_direct_download)
+      end
+
+      def signed_head_url(file)
+        fog_storage = ::Fog::Storage.new(file.fog_credentials)
+        fog_dir = fog_storage.directories.new(key: file.fog_directory)
+        fog_file = fog_dir.files.new(key: file.path)
+        expire_at = ::Fog::Time.now + file.fog_authenticated_url_expiration
+
+        fog_file.collection.head_url(fog_file.key, expire_at)
+      end
+
+      def head_request_on_aws_file?(file, supports_direct_download)
+        Gitlab.config.packages.object_store.enabled &&
+          supports_direct_download &&
+          file.class.direct_download_enabled? &&
+          request.head? &&
+          file.fog_credentials[:provider] == 'AWS'
+      end
     end
 
     desc 'Download the maven package file at instance level' do
@@ -85,8 +110,7 @@ def jar_file?(format)
         package_file.file_sha1
       else
         track_event('pull_package') if jar_file?(format)
-
-        present_carrierwave_file!(package_file.file)
+        present_carrierwave_file_with_head_support!(package_file.file)
       end
     end
 
@@ -126,7 +150,7 @@ def jar_file?(format)
         else
           track_event('pull_package') if jar_file?(format)
 
-          present_carrierwave_file!(package_file.file)
+          present_carrierwave_file_with_head_support!(package_file.file)
         end
       end
     end
@@ -166,7 +190,7 @@ def jar_file?(format)
         else
           track_event('pull_package') if jar_file?(format)
 
-          present_carrierwave_file!(package_file.file)
+          present_carrierwave_file_with_head_support!(package_file.file)
         end
       end
 
diff --git a/ee/spec/requests/api/maven_packages_spec.rb b/ee/spec/requests/api/maven_packages_spec.rb
index 64f9d03e5559ce9e316aa097be8f3ebdfadce8e4..a6211de5f18ea3c9eb699f25c4a1f22b47079a36 100644
--- a/ee/spec/requests/api/maven_packages_spec.rb
+++ b/ee/spec/requests/api/maven_packages_spec.rb
@@ -29,6 +29,54 @@
     end
   end
 
+  shared_examples 'processing HEAD requests' do
+    subject { head api(url) }
+
+    before do
+      allow_any_instance_of(::Packages::PackageFileUploader).to receive(:fog_credentials).and_return(object_storage_credentials)
+      stub_package_file_object_storage(enabled: object_storage_enabled)
+    end
+
+    context 'with object storage enabled' do
+      let(:object_storage_enabled) { true }
+
+      before do
+        allow_any_instance_of(::Packages::PackageFileUploader).to receive(:file_storage?).and_return(false)
+      end
+
+      context 'non AWS provider' do
+        let(:object_storage_credentials) { { provider: 'Google' } }
+
+        it 'does not generated a signed url for head' do
+          expect_any_instance_of(Fog::AWS::Storage::Files).not_to receive(:head_url)
+
+          subject
+        end
+      end
+
+      context 'with AWS provider' do
+        let(:object_storage_credentials) { { provider: 'AWS', aws_access_key_id: 'test', aws_secret_access_key: 'test' } }
+
+        it 'generates a signed url for head' do
+          expect_any_instance_of(Fog::AWS::Storage::Files).to receive(:head_url).and_call_original
+
+          subject
+        end
+      end
+    end
+
+    context 'with object storage disabled' do
+      let(:object_storage_enabled) { false }
+      let(:object_storage_credentials) { {} }
+
+      it 'does not generate a signed url for head' do
+        expect_any_instance_of(Fog::AWS::Storage::Files).not_to receive(:head_url)
+
+        subject
+      end
+    end
+  end
+
   describe 'GET /api/v4/packages/maven/*path/:file_name' do
     let(:package) { create(:maven_package, project: project, name: project.full_path) }
 
@@ -149,6 +197,15 @@ def download_file_with_token(file_name, params = {}, request_headers = headers_w
     end
   end
 
+  describe 'HEAD /api/v4/packages/maven/*path/:file_name' do
+    let_it_be(:project) { create(:project, :public) }
+    let_it_be(:package) { create(:maven_package, project: project, name: project.full_path) }
+    let_it_be(:package_file) { package.package_files.where('file_name like ?', '%.xml').first }
+    let(:url) { "/packages/maven/#{package.maven_metadatum.path}/#{package_file.file_name}" }
+
+    it_behaves_like 'processing HEAD requests'
+  end
+
   describe 'GET /api/v4/groups/:id/-/packages/maven/*path/:file_name' do
     before do
       project.team.truncate
@@ -262,6 +319,16 @@ def download_file_with_token(file_name, params = {}, request_headers = headers_w
     end
   end
 
+  describe 'HEAD /api/v4/groups/:id/-/packages/maven/*path/:file_name' do
+    let_it_be(:group) { create(:group) }
+    let_it_be(:project) { create(:project, :public, namespace: group) }
+    let_it_be(:package) { create(:maven_package, project: project, name: project.full_path) }
+    let_it_be(:package_file) { package.package_files.where('file_name like ?', '%.xml').first }
+    let(:url) { "/groups/#{group.id}/-/packages/maven/#{package.maven_metadatum.path}/#{package_file.file_name}" }
+
+    it_behaves_like 'processing HEAD requests'
+  end
+
   describe 'GET /api/v4/projects/:id/packages/maven/*path/:file_name' do
     context 'a public project' do
       subject { download_file(package_file.file_name) }
@@ -340,6 +407,15 @@ def download_file_with_token(file_name, params = {}, request_headers = headers_w
     end
   end
 
+  describe 'HEAD /api/v4/projects/:id/packages/maven/*path/:file_name' do
+    let_it_be(:project) { create(:project, :public) }
+    let_it_be(:package) { create(:maven_package, project: project, name: project.full_path) }
+    let_it_be(:package_file) { package.package_files.where('file_name like ?', '%.xml').first }
+    let(:url) { "/projects/#{project.id}/packages/maven/#{package.maven_metadatum.path}/#{package_file.file_name}" }
+
+    it_behaves_like 'processing HEAD requests'
+  end
+
   describe 'PUT /api/v4/projects/:id/packages/maven/*path/:file_name/authorize' do
     it 'rejects a malicious request' do
       put api("/projects/#{project.id}/packages/maven/com/example/my-app/#{version}/%2e%2e%2F.ssh%2Fauthorized_keys/authorize"), params: {}, headers: headers_with_token