diff --git a/app/services/merge_requests/push_options_handler_service.rb b/app/services/merge_requests/push_options_handler_service.rb
index cc1e08e16062ef0e6035e30f921e0fd4fbb956b1..ad0b033f082369a329a642761005d51ac6cb4e1b 100644
--- a/app/services/merge_requests/push_options_handler_service.rb
+++ b/app/services/merge_requests/push_options_handler_service.rb
@@ -142,6 +142,8 @@ def base_params
       params[:add_assignee_ids] = params.delete(:assign).keys if params.has_key?(:assign)
       params[:remove_assignee_ids] = params.delete(:unassign).keys if params.has_key?(:unassign)
 
+      params[:milestone] = project.milestones&.find_by_name(push_options[:milestone]) if push_options[:milestone]
+
       params
     end
 
diff --git a/doc/user/project/push_options.md b/doc/user/project/push_options.md
index 728c682b0099884e58e3d98133c9f155ad45a80e..46619c96f2f1ce247c003a73320bc05ac0fbf58f 100644
--- a/doc/user/project/push_options.md
+++ b/doc/user/project/push_options.md
@@ -66,6 +66,7 @@ time as pushing changes:
 | `merge_request.remove_source_branch`         | Set the merge request to remove the source branch when it's merged.                                             | [12.2](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/64320)          |
 | `merge_request.title="<title>"`              | Set the title of the merge request. Ex: `git push -o merge_request.title="The title I want"`.                   | [12.2](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/64320)          |
 | `merge_request.description="<description>"`  | Set the description of the merge request. Ex: `git push -o merge_request.description="The description I want"`. | [12.2](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/64320)          |
+| `merge_request.milestone="<milestone>"`      | Set the milestone of the merge request. Ex: `git push -o merge_request.milestone="3.0"`.                        | [14.1](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/63960)       |
 | `merge_request.label="<label>"`              | Add labels to the merge request. If the label does not exist, it is created. For example, for two labels: `git push -o merge_request.label="label1" -o merge_request.label="label2"`. | [12.3](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/31831) |
 | `merge_request.unlabel="<label>"`            | Remove labels from the merge request. For example, for two labels: `git push -o merge_request.unlabel="label1" -o merge_request.unlabel="label2"`. | [12.3](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/31831) |
 | `merge_request.assign="<user>"`              | Assign users to the merge request. For example, for two users: `git push -o merge_request.assign="user1" -o merge_request.assign="user2"`. | [13.10](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/25904) |
diff --git a/lib/gitlab/push_options.rb b/lib/gitlab/push_options.rb
index ce9fced946550684eb8a4e85f42b2971ceee2948..9d954a74948d874237da4186abef7739f6dab579 100644
--- a/lib/gitlab/push_options.rb
+++ b/lib/gitlab/push_options.rb
@@ -10,6 +10,7 @@ class PushOptions
           :description,
           :label,
           :merge_when_pipeline_succeeds,
+          :milestone,
           :remove_source_branch,
           :target,
           :title,
diff --git a/spec/services/merge_requests/push_options_handler_service_spec.rb b/spec/services/merge_requests/push_options_handler_service_spec.rb
index 87c3fc6a2d8a105f93531cefb2edef38aa2e7e84..c09435a70e221810858aa1b354a3c594430d5a60 100644
--- a/spec/services/merge_requests/push_options_handler_service_spec.rb
+++ b/spec/services/merge_requests/push_options_handler_service_spec.rb
@@ -10,6 +10,7 @@
   let_it_be(:user2) { create(:user, developer_projects: [project]) }
   let_it_be(:user3) { create(:user, developer_projects: [project]) }
   let_it_be(:forked_project) { fork_project(project, user1, repository: true) }
+  let_it_be(:milestone) { create(:milestone, project: project, title: '1.0') }
 
   let(:service) { described_class.new(project: project, current_user: user1, changes: changes, push_options: push_options) }
   let(:source_branch) { 'fix' }
@@ -59,6 +60,16 @@
     end
   end
 
+  shared_examples_for 'a service that can set the milestone of a merge request' do
+    subject(:last_mr) { MergeRequest.last }
+
+    it 'sets the milestone' do
+      service.execute
+
+      expect(last_mr.milestone&.title).to eq(expected_milestone)
+    end
+  end
+
   shared_examples_for 'a service that can set the merge request to merge when pipeline succeeds' do
     subject(:last_mr) { MergeRequest.last }
 
@@ -514,6 +525,70 @@
     it_behaves_like 'with the project default branch'
   end
 
+  describe '`milestone` push option' do
+    context 'with a valid milestone' do
+      let(:expected_milestone) { milestone.title }
+      let(:push_options) { { milestone: milestone.title } }
+
+      context 'with a new branch' do
+        let(:changes) { new_branch_changes }
+
+        it_behaves_like 'a service that does not create a merge request'
+
+        it 'adds an error to the service' do
+          service.execute
+
+          expect(service.errors).to include(error_mr_required)
+        end
+
+        context 'when coupled with the `create` push option' do
+          let(:push_options) { { create: true, milestone: milestone.title } }
+
+          it_behaves_like 'a service that can create a merge request'
+          it_behaves_like 'a service that can set the milestone of a merge request'
+        end
+      end
+
+      context 'with an existing branch but no open MR' do
+        let(:changes) { existing_branch_changes }
+
+        it_behaves_like 'a service that does not create a merge request'
+
+        it 'adds an error to the service' do
+          service.execute
+
+          expect(service.errors).to include(error_mr_required)
+        end
+
+        context 'when coupled with the `create` push option' do
+          let(:push_options) { { create: true, milestone: milestone.title } }
+
+          it_behaves_like 'a service that can create a merge request'
+          it_behaves_like 'a service that can set the milestone of a merge request'
+        end
+      end
+
+      context 'with an existing branch that has a merge request open' do
+        let(:changes) { existing_branch_changes }
+        let!(:merge_request) { create(:merge_request, source_project: project, source_branch: source_branch)}
+
+        it_behaves_like 'a service that does not create a merge request'
+        it_behaves_like 'a service that can set the milestone of a merge request'
+      end
+
+      it_behaves_like 'with a deleted branch'
+      it_behaves_like 'with the project default branch'
+    end
+
+    context 'with invalid milestone' do
+      let(:expected_milestone) { nil }
+      let(:changes) { new_branch_changes }
+      let(:push_options) { { create: true, milestone: 'invalid_milestone' } }
+
+      it_behaves_like 'a service that can set the milestone of a merge request'
+    end
+  end
+
   shared_examples 'with an existing branch that has a merge request open in foss' do
     let(:changes) { existing_branch_changes }
     let!(:merge_request) { create(:merge_request, source_project: project, source_branch: source_branch)}