diff --git a/app/controllers/projects/pages_controller.rb b/app/controllers/projects/pages_controller.rb
index 7500ae6cc8a460c8100dfd5afe463d840d4f481a..a20bd6ac1ec48dbb53f42c28940933646b620451 100644
--- a/app/controllers/projects/pages_controller.rb
+++ b/app/controllers/projects/pages_controller.rb
@@ -53,6 +53,23 @@ def update
     end
   end
 
+  def regenerate_unique_domain
+    return render_403 unless can?(current_user, :update_pages, @project)
+    return render_403 unless @project.project_setting.pages_unique_domain_enabled?
+
+    result = Gitlab::Pages.generate_unique_domain(@project)
+
+    respond_to do |format|
+      format.html do
+        if result && @project.project_setting.update(pages_unique_domain: result)
+          redirect_to project_pages_path(@project), notice: _('Successfully regenerated unique domain')
+        else
+          redirect_to project_pages_path(@project), alert: _('Failed to regenerate unique domain')
+        end
+      end
+    end
+  end
+
   private
 
   def project_params
diff --git a/app/views/projects/pages/_access.html.haml b/app/views/projects/pages/_access.html.haml
index e357f6a05b8531f2065c0fbd72449cb275907141..9620b7c7339263d1d672db48d78d5a3684c40279 100644
--- a/app/views/projects/pages/_access.html.haml
+++ b/app/views/projects/pages/_access.html.haml
@@ -6,8 +6,16 @@
     icon: 'doc-text',
     count: @project.pages_domains.size + (pages_url ? 1 : 0),
     options: { class: 'gl-mt-5', data: { testid: 'access-page-container' } },
-    footer_options: { class: 'gl-bg-red-50' }) do |c|
-    - c.with_body do
+    footer_options: { class: 'gl-bg-red-50' }) do |content|
+    - if @project.project_setting.pages_unique_domain_enabled?
+      - content.with_actions do
+        = render Pajamas::ButtonComponent.new(href: regenerate_unique_domain_project_pages_path(@project),
+          method: :post,
+          size: :small,
+          button_options: { data: { confirm: s_('GitLabPages|Are you sure you want to regenerate the unique domain? The previous URL will stop working.'), 'confirm-btn-variant': 'danger' },
+          "aria-label": s_('GitLabPages|Regenerate unique domain') }) do
+          = s_('GitLabPages|Regenerate unique domain')
+    - content.with_body do
       %ul.content-list
         %li
           = external_link(pages_url_text, pages_url)
@@ -17,7 +25,7 @@
             = external_link(domain.url, domain.url)
 
     - unless @project.public_pages?
-      - c.with_footer do
+      - content.with_footer do
         - help_page = help_page_path('user/project/pages/pages_access_control.md')
         - link_start = '<a href="%{url}" target="_blank" class="gl-alert-link" rel="noopener noreferrer">'.html_safe % { url: help_page }
         - link_end = '</a>'.html_safe
diff --git a/config/routes/project.rb b/config/routes/project.rb
index fb7b9a3e5cff5fe1af2e6ae9ac298b78af24aa95..9f5fd1c5949d19eb2a43814357425f89a3e16432 100644
--- a/config/routes/project.rb
+++ b/config/routes/project.rb
@@ -530,7 +530,8 @@
         defaults: { format: 'json' },
         constraints: { template_type: %r{issue|merge_request}, format: 'json' }
 
-      resource :pages, only: [:new, :show, :update, :destroy] do # rubocop: disable Cop/PutProjectRoutesUnderScope
+      resource :pages, only: [:new, :show, :update, :destroy, :regenerate_unique_domain] do # rubocop: disable Cop/PutProjectRoutesUnderScope
+        post :regenerate_unique_domain # rubocop:todo Cop/PutProjectRoutesUnderScope
         resources :domains, except: :index, controller: 'pages_domains', constraints: { id: %r{[^/]+} } do # rubocop: disable Cop/PutProjectRoutesUnderScope
           member do
             post :verify # rubocop:todo Cop/PutProjectRoutesUnderScope
diff --git a/doc/user/project/pages/introduction.md b/doc/user/project/pages/introduction.md
index d06d4008e8c637bd9c2327219e3ca1fa2eb877b0..b8a5d1799a41401d7876966efffcd9e4a5491ea0 100644
--- a/doc/user/project/pages/introduction.md
+++ b/doc/user/project/pages/introduction.md
@@ -298,6 +298,26 @@ as artifacts, and GitLab doesn't know which one you want to deploy.
 
 The previous YAML example uses [user-defined job names](index.md#user-defined-job-names).
 
+## Regenerate unique domain for GitLab Pages
+
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/481746) in GitLab 17.7.
+
+You can regenerate the unique domain for your GitLab Pages site.
+
+After the domain is regenerated, the previous URL is no longer active.
+If anyone tries to access the old URL, they'll receive a `404` error.
+
+Prerequisites
+
+- You must have at least the Maintainer role for the project.
+- The **Use unique domain** setting [must be enabled](index.md#unique-domains) in your project's Pages settings.
+
+To regenerate a unique domain for your GitLab Pages site:
+
+1. On the left sidebar, select  **Deploy > Pages**.
+1. Next to **Access pages**, press **Regenerate unique domain**.
+1. GitLab generates a new unique domain for your Pages site.
+
 ## Known issues
 
 For a list of known issues, see the GitLab [public issue tracker](https://gitlab.com/gitlab-org/gitlab/-/issues?label_name[]=Category%3APages).
diff --git a/lib/gitlab/pages.rb b/lib/gitlab/pages.rb
index f2a5d6f4f8507641efe1673c761f025e996438a9..2596713120961aa8a7279a387287ba929372c821 100644
--- a/lib/gitlab/pages.rb
+++ b/lib/gitlab/pages.rb
@@ -57,11 +57,12 @@ def multiple_versions_enabled_for?(project)
           project.licensed_feature_available?(:pages_multiple_versions)
       end
 
-      private
-
       def generate_unique_domain(project)
         10.times do
           pages_unique_domain = Gitlab::Pages::RandomDomain.generate(project_path: project.path)
+
+          return false if pages_unique_domain.blank?
+
           return pages_unique_domain unless
             ProjectSetting.unique_domain_exists?(pages_unique_domain) ||
               Namespace.top_level.by_path(pages_unique_domain).present?
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index f38f1f073dd2dde0a629d3ba7cd95a68bcb7296d..53f0c0ecaa120a00b718790201783138b44e39e8 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -23398,6 +23398,9 @@ msgstr ""
 msgid "Failed to publish issue on status page."
 msgstr ""
 
+msgid "Failed to regenerate unique domain"
+msgstr ""
+
 msgid "Failed to remove a Zoom meeting"
 msgstr ""
 
@@ -25271,6 +25274,9 @@ msgstr ""
 msgid "GitLabPages|Access pages"
 msgstr ""
 
+msgid "GitLabPages|Are you sure you want to regenerate the unique domain? The previous URL will stop working."
+msgstr ""
+
 msgid "GitLabPages|Are you sure?"
 msgstr ""
 
@@ -25310,6 +25316,9 @@ msgstr ""
 msgid "GitLabPages|Pages"
 msgstr ""
 
+msgid "GitLabPages|Regenerate unique domain"
+msgstr ""
+
 msgid "GitLabPages|Remove certificate"
 msgstr ""
 
@@ -54604,6 +54613,9 @@ msgstr ""
 msgid "Successfully linked ID(s): %{item_ids}."
 msgstr ""
 
+msgid "Successfully regenerated unique domain"
+msgstr ""
+
 msgid "Successfully removed email."
 msgstr ""
 
diff --git a/spec/controllers/projects/pages_controller_spec.rb b/spec/controllers/projects/pages_controller_spec.rb
index a9fc8082ae9b497068dc7353ffb2d386dd0d1ae4..8afc4ed7b805e926057828b29a7b3c16c62535e0 100644
--- a/spec/controllers/projects/pages_controller_spec.rb
+++ b/spec/controllers/projects/pages_controller_spec.rb
@@ -219,4 +219,87 @@
       end
     end
   end
+
+  describe 'POST regenerate_unique_domain' do
+    before do
+      project.project_setting.update!(
+        pages_unique_domain_enabled: true,
+        pages_unique_domain: 'pages-abcde'
+      )
+    end
+
+    context 'when update is successful' do
+      it 'redirects with success message' do
+        original_domain = project.project_setting.pages_unique_domain
+
+        post :regenerate_unique_domain, params: { namespace_id: project.namespace, project_id: project }
+
+        expect(response).to redirect_to(project_pages_path(project))
+        expect(flash[:notice]).to eq('Successfully regenerated unique domain')
+        project.reload
+        expect(project.project_setting.pages_unique_domain).not_to eq(original_domain)
+        expect(project.project_setting.pages_unique_domain).to be_present
+      end
+    end
+
+    context 'when update fails' do
+      before do
+        allow(Gitlab::Pages::RandomDomain).to receive(:generate).and_return(false)
+      end
+
+      it 'redirects with error message when domain regeneration fails' do
+        post :regenerate_unique_domain, params: { namespace_id: project.namespace, project_id: project }
+
+        expect(response).to redirect_to(project_pages_path(project))
+        expect(flash[:alert]).to eq('Failed to regenerate unique domain')
+      end
+
+      it 'redirects with error message when project setting update fails' do
+        allow_next_instance_of(ProjectSetting) do |instance|
+          allow(instance).to receive(:update).and_return(false)
+        end
+
+        post :regenerate_unique_domain, params: { namespace_id: project.namespace, project_id: project }
+
+        expect(response).to redirect_to(project_pages_path(project))
+        expect(flash[:alert]).to eq('Failed to regenerate unique domain')
+      end
+    end
+
+    context 'when user does not have permission' do
+      before do
+        project.add_developer(user)
+      end
+
+      it 'returns 404' do
+        post :regenerate_unique_domain, params: { namespace_id: project.namespace, project_id: project }
+
+        expect(response).to have_gitlab_http_status(:not_found)
+      end
+    end
+
+    context 'when unique domains is not enabled' do
+      before do
+        project.project_setting.update!(pages_unique_domain_enabled: false)
+      end
+
+      it 'returns 403' do
+        post :regenerate_unique_domain, params: { namespace_id: project.namespace, project_id: project }
+
+        expect(response).to have_gitlab_http_status(:forbidden)
+      end
+    end
+
+    context 'when pages is disabled' do
+      before do
+        allow(Gitlab.config.pages).to receive(:enabled).and_return(false)
+      end
+
+      it 'returns 404 status' do
+        post :regenerate_unique_domain, params: { namespace_id: project.namespace, project_id: project }
+
+        expect(response).to have_gitlab_http_status(:not_found)
+      end
+    end
+  end
 end
diff --git a/spec/lib/gitlab/pages_spec.rb b/spec/lib/gitlab/pages_spec.rb
index dcf102d8e1c6330192174bcbaacfc75c446cd827..907ea6511ad8d4ab0cce678eb7a418e2584e1500 100644
--- a/spec/lib/gitlab/pages_spec.rb
+++ b/spec/lib/gitlab/pages_spec.rb
@@ -177,6 +177,78 @@
     end
   end
 
+  describe '.generate_unique_domain' do
+    let(:project) { create(:project, path: 'test-project') }
+
+    context 'when a unique domain can be generated' do
+      before do
+        allow(Gitlab::Pages::RandomDomain).to receive(:generate)
+          .with(project_path: project.path)
+          .and_return('unique-domain-123')
+
+        allow(ProjectSetting).to receive(:unique_domain_exists?)
+          .with('unique-domain-123')
+          .and_return(false)
+      end
+
+      it 'returns the generated unique domain' do
+        expect(described_class.generate_unique_domain(project)).to eq('unique-domain-123')
+      end
+
+      it 'attempts generation only once when first attempt succeeds' do
+        expect(Gitlab::Pages::RandomDomain).to receive(:generate).once
+
+        described_class.generate_unique_domain(project)
+      end
+    end
+
+    context 'when first attempts fail but later succeeds' do
+      before do
+        # First two attempts generate existing domains
+        allow(Gitlab::Pages::RandomDomain).to receive(:generate)
+          .with(project_path: project.path)
+          .and_return('existing-domain-1', 'existing-domain-2', 'unique-domain-123')
+
+        allow(ProjectSetting).to receive(:unique_domain_exists?)
+          .with('existing-domain-1').and_return(true)
+        allow(ProjectSetting).to receive(:unique_domain_exists?)
+          .with('existing-domain-2').and_return(true)
+        allow(ProjectSetting).to receive(:unique_domain_exists?)
+          .with('unique-domain-123').and_return(false)
+      end
+
+      it 'returns the first unique domain generated' do
+        expect(described_class.generate_unique_domain(project)).to eq('unique-domain-123')
+      end
+    end
+
+    context 'when unique domain generation fails after all attempts' do
+      before do
+        allow(Gitlab::Pages::RandomDomain).to receive(:generate)
+          .with(project_path: project.path)
+          .and_return('existing-domain')
+
+        allow(ProjectSetting).to receive(:unique_domain_exists?)
+          .with('existing-domain')
+          .and_return(true)
+      end
+
+      it 'raises UniqueDomainGenerationFailure after 10 attempts' do
+        expect(Gitlab::Pages::RandomDomain).to receive(:generate).exactly(10).times
+
+        expect { described_class.generate_unique_domain(project) }
+          .to raise_error(Gitlab::Pages::UniqueDomainGenerationFailure)
+      end
+    end
+
+    context 'when project is nil' do
+      it 'raises NoMethodError' do
+        expect { described_class.generate_unique_domain(nil) }
+          .to raise_error(NoMethodError)
+      end
+    end
+  end
+
   describe '#update_default_domain_redirect' do
     let(:project) { build(:project) }