diff --git a/ee/lib/api/geo.rb b/ee/lib/api/geo.rb
index 18a8444023fe2db9000a844d64095a38e32dc0a5..167ee6301bf82466919c18ba7d9a5716c17ae512 100644
--- a/ee/lib/api/geo.rb
+++ b/ee/lib/api/geo.rb
@@ -1,3 +1,5 @@
+require 'base64'
+
 module API
   class Geo < Grape::API
     resource :geo do
@@ -40,6 +42,51 @@ class Geo < Grape::API
           render_validation_error!(db_status)
         end
       end
+
+      # git push over SSH secondary -> primary related proxying logic
+      #
+      resource 'proxy_git_push_ssh' do
+        format :json
+
+        # Responsible for making HTTP GET /repo.git/info/refs?service=git-receive-pack
+        # request *from* secondary gitlab-shell to primary
+        #
+        params do
+          requires :secret_token, type: String
+          requires :data, type: Hash do
+            requires :gl_id, type: String
+            requires :primary_repo, type: String
+          end
+        end
+        post 'info_refs' do
+          authenticate_by_gitlab_shell_token!
+          params.delete(:secret_token)
+
+          resp = Gitlab::Geo::GitPushSSHProxy.new(params['data']).info_refs
+          status(resp.code.to_i)
+          { status: true, message: nil, result: Base64.encode64(resp.body.to_s) }
+        end
+
+        # Responsible for making HTTP POST /repo.git/git-receive-pack
+        # request *from* secondary gitlab-shell to primary
+        #
+        params do
+          requires :secret_token, type: String
+          requires :data, type: Hash do
+            requires :gl_id, type: String
+            requires :primary_repo, type: String
+          end
+          requires :output, type: String, desc: 'Output from git-receive-pack'
+        end
+        post 'push' do
+          authenticate_by_gitlab_shell_token!
+          params.delete(:secret_token)
+
+          resp = Gitlab::Geo::GitPushSSHProxy.new(params['data']).push(Base64.decode64(params['output']))
+          status(resp.code.to_i)
+          { status: true, message: nil, result: Base64.encode64(resp.body.to_s) }
+        end
+      end
     end
   end
 end
diff --git a/ee/lib/ee/gitlab/middleware/read_only/controller.rb b/ee/lib/ee/gitlab/middleware/read_only/controller.rb
index a8705a706ac14119bc1bfcf1d18994ee1309cb0b..0d7c04497d92cac0db23b4a22871e95e3f4de9cb 100644
--- a/ee/lib/ee/gitlab/middleware/read_only/controller.rb
+++ b/ee/lib/ee/gitlab/middleware/read_only/controller.rb
@@ -17,7 +17,7 @@ module Controller
 
           override :whitelisted_routes
           def whitelisted_routes
-            super || geo_node_update_route
+            super || geo_node_update_route || geo_proxy_git_push_ssh_route
           end
 
           def geo_node_update_route
@@ -33,6 +33,14 @@ def geo_node_update_route
               WHITELISTED_GEO_ROUTES_TRACKING_DB[controller]&.include?(action)
             end
           end
+
+          def geo_proxy_git_push_ssh_route
+            routes = ::Gitlab::Middleware::ReadOnly::API_VERSIONS.map do |version|
+              ["/api/v#{version}/geo/proxy_git_push_ssh/info_refs",
+               "/api/v#{version}/geo/proxy_git_push_ssh/push"]
+            end
+            routes.flatten.include?(request.path)
+          end
         end
       end
     end
diff --git a/ee/spec/requests/api/geo_spec.rb b/ee/spec/requests/api/geo_spec.rb
index d95bb7bbd84263803d5b631f09cd67262f29f57a..a917d007eded17a431348f8f2137589d08f662b1 100644
--- a/ee/spec/requests/api/geo_spec.rb
+++ b/ee/spec/requests/api/geo_spec.rb
@@ -25,7 +25,7 @@
     end
   end
 
-  describe '/geo/transfers' do
+  describe 'GET /geo/transfers' do
     before do
       stub_current_geo_node(secondary_node)
     end
@@ -287,4 +287,119 @@
       it_behaves_like 'with terms enforced'
     end
   end
+
+  describe '/geo/proxy_git_push_ssh' do
+    let(:secret_token) { Gitlab::Shell.secret_token }
+    let(:data) { { primary_repo: 'http://localhost:3001/testuser/repo.git', gl_id: 'key-1', gl_username: 'testuser' } }
+
+    before do
+      stub_current_geo_node(secondary_node)
+    end
+
+    describe 'POST /geo/proxy_git_push_ssh/info_refs' do
+      context 'with all required params missing' do
+        it 'responds with 400' do
+          post api('/geo/proxy_git_push_ssh/info_refs'), nil
+
+          expect(response).to have_gitlab_http_status(400)
+          expect(json_response['error']).to eql('secret_token is missing, data is missing, data[gl_id] is missing, data[primary_repo] is missing')
+        end
+      end
+
+      context 'with all required params' do
+        let(:git_push_ssh_proxy) { double(Gitlab::Geo::GitPushSSHProxy) }
+
+        before do
+          allow(Gitlab::Geo::GitPushSSHProxy).to receive(:new).with(data).and_return(git_push_ssh_proxy)
+        end
+
+        context 'with an invalid secret_token' do
+          it 'responds with 401' do
+            post(api('/geo/proxy_git_push_ssh/info_refs'), { secret_token: 'invalid', data: data })
+
+            expect(response).to have_gitlab_http_status(401)
+            expect(json_response['error']).to be_nil
+          end
+        end
+
+        context 'where an exception occurs' do
+          it 'responds with 500' do
+            expect(git_push_ssh_proxy).to receive(:info_refs).and_raise('deliberate exception raised')
+
+            post api('/geo/proxy_git_push_ssh/info_refs'), { secret_token: secret_token, data: data }
+
+            expect(response).to have_gitlab_http_status(500)
+            expect(json_response['message']).to include('RuntimeError (deliberate exception raised)')
+            expect(json_response['result']).to be_nil
+          end
+        end
+
+        context 'with a valid secret token' do
+          let(:http_response) { double(Net::HTTPResponse, code: 200, body: 'something here') }
+
+          it 'responds with 200' do
+            expect(git_push_ssh_proxy).to receive(:info_refs).and_return(http_response)
+
+            post api('/geo/proxy_git_push_ssh/info_refs'), { secret_token: secret_token, data: data }
+
+            expect(response).to have_gitlab_http_status(200)
+            expect(json_response['result']).to eql('something+here')
+          end
+        end
+      end
+    end
+
+    describe 'POST /geo/proxy_git_push_ssh/push' do
+      context 'with all required params missing' do
+        it 'responds with 400' do
+          post api('/geo/proxy_git_push_ssh/push'), nil
+
+          expect(response).to have_gitlab_http_status(400)
+          expect(json_response['error']).to eql('secret_token is missing, data is missing, data[gl_id] is missing, data[primary_repo] is missing, output is missing')
+        end
+      end
+
+      context 'with all required params' do
+        let(:output) { 'output text'}
+        let(:git_push_ssh_proxy) { double(Gitlab::Geo::GitPushSSHProxy) }
+
+        before do
+          allow(Gitlab::Geo::GitPushSSHProxy).to receive(:new).with(data).and_return(git_push_ssh_proxy)
+        end
+
+        context 'with an invalid secret_token' do
+          it 'responds with 401' do
+            post(api('/geo/proxy_git_push_ssh/push'), { secret_token: 'invalid', data: data, output: output })
+
+            expect(response).to have_gitlab_http_status(401)
+            expect(json_response['error']).to be_nil
+          end
+        end
+
+        context 'where an exception occurs' do
+          it 'responds with 500' do
+            expect(git_push_ssh_proxy).to receive(:push).and_raise('deliberate exception raised')
+            post api('/geo/proxy_git_push_ssh/push'), { secret_token: secret_token, data: data, output: output }
+
+            expect(response).to have_gitlab_http_status(500)
+            expect(json_response['message']).to include('RuntimeError (deliberate exception raised)')
+            expect(json_response['result']).to be_nil
+          end
+        end
+
+        context 'with a valid secret token' do
+          let(:http_response) { double(Net::HTTPResponse, code: 201, body: 'something here') }
+
+          it 'responds with 201' do
+            expect(git_push_ssh_proxy).to receive(:push).with(output).and_return(http_response)
+
+            post api('/geo/proxy_git_push_ssh/push'), { secret_token: secret_token, data: data, output: output }
+
+            expect(response).to have_gitlab_http_status(201)
+            expect(json_response['result']).to eql('something+here')
+          end
+        end
+      end
+    end
+  end
 end