diff --git a/doc/user/admin_area/license.md b/doc/user/admin_area/license.md
index d5087c20e6f11a813a3ee83022507420ed49e051..c3f0c94db218ecd466bd4856723918112078850e 100644
--- a/doc/user/admin_area/license.md
+++ b/doc/user/admin_area/license.md
@@ -94,7 +94,7 @@ a license, upload the license in the **Admin Area** in the web user interface.
 
 ## What happens when your license expires
 
-One month before the license expires, a message with the upcoming expiration
+Fifteen days before the license expires, a notification banner with the upcoming expiration
 date displays to GitLab administrators.
 
 When your license expires, GitLab locks features, like Git pushes
diff --git a/ee/app/models/license.rb b/ee/app/models/license.rb
index 309c5a017480f36b22de6978aa44ef538861b45c..a3e205d03c9aafeaf927a8a8c06ef1f3a526da17 100644
--- a/ee/app/models/license.rb
+++ b/ee/app/models/license.rb
@@ -11,6 +11,9 @@ class License < ApplicationRecord
   LICENSE_FILE_TYPE = 'license_file'
   ALLOWED_PERCENTAGE_OF_USERS_OVERAGE = (10 / 100.0)
 
+  NOTIFICATION_DAYS_BEFORE_TRIAL_EXPIRY = 1.week
+  ADMIN_NOTIFICATION_DAYS_BEFORE_EXPIRY = 15.days
+
   EE_ALL_PLANS = [STARTER_PLAN, PREMIUM_PLAN, ULTIMATE_PLAN].freeze
 
   EES_FEATURES_WITH_USAGE_PING = %i[
@@ -623,6 +626,25 @@ class License < ApplicationRecord
     super || created_at
   end
 
+  # Overrides method from Gitlab::License which will be removed in a future version
+  def notify_admins?
+    return false if expires_at.blank?
+    return true if expired?
+
+    notification_days = trial? ? NOTIFICATION_DAYS_BEFORE_TRIAL_EXPIRY : ADMIN_NOTIFICATION_DAYS_BEFORE_EXPIRY
+
+    Date.current >= (expires_at - notification_days)
+  end
+
+  # Overrides method from Gitlab::License which will be removed in a future version
+  def notify_users?
+    return false if expires_at.blank?
+
+    notification_start_date = trial? ? expires_at - NOTIFICATION_DAYS_BEFORE_TRIAL_EXPIRY : block_changes_at
+
+    Date.current >= notification_start_date
+  end
+
   private
 
   def restricted_attr(name, default = nil)
diff --git a/ee/spec/features/subscriptions/expiring_subscription_message_spec.rb b/ee/spec/features/subscriptions/expiring_subscription_message_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..274989e7f23f19e048fa71cc87c50d32aaa98f18
--- /dev/null
+++ b/ee/spec/features/subscriptions/expiring_subscription_message_spec.rb
@@ -0,0 +1,154 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Expiring Subscription Message', :js, :freeze_time do
+  shared_examples 'no expiration notification' do
+    it 'loads the page without any subscription expiration notifications' do
+      expect(page).not_to have_content('Your subscription expired!')
+      expect(page).not_to have_content('Your subscription will expire')
+    end
+  end
+
+  context 'for self-managed subscriptions' do
+    context 'when signed in user is an admin' do
+      let_it_be(:admin) { create(:admin) }
+
+      before do
+        create_current_license(plan: License::ULTIMATE_PLAN, expires_at: expires_at)
+
+        sign_in(admin)
+        gitlab_enable_admin_mode_sign_in(admin)
+      end
+
+      context 'with a license with no expiration' do
+        let(:expires_at) { nil }
+
+        include_examples 'no expiration notification'
+      end
+
+      context 'with an expired license' do
+        let(:expires_at) { Date.current - 1.day }
+
+        it 'notifies the admin of the expired subscription' do
+          expect(page).to have_content('Your subscription expired!')
+        end
+      end
+
+      context 'with a license expiring in 15 days' do
+        let(:expires_at) { Date.current + 15.days }
+
+        it 'notifies the admin of a soon expiring subscription' do
+          expect(page).to have_content('Your subscription will expire in 15 days')
+        end
+      end
+
+      context 'with a license expiring in more than 15 days' do
+        let(:expires_at) { Date.current + 16.days }
+
+        include_examples 'no expiration notification'
+      end
+    end
+
+    context 'when signed in user is not an admin' do
+      let_it_be(:user) { create(:user) }
+
+      before do
+        create_current_license(plan: License::ULTIMATE_PLAN, expires_at: expires_at, block_changes_at: block_changes_at)
+
+        sign_in(user)
+        visit root_path
+      end
+
+      context 'with a license with no expiration' do
+        let(:expires_at) { nil }
+        let(:block_changes_at) { nil }
+
+        include_examples 'no expiration notification'
+      end
+
+      context 'with an expired license in the grace period' do
+        let(:expires_at) { Date.current - 1.day }
+        let(:block_changes_at) { Date.current + 13.days }
+
+        include_examples 'no expiration notification'
+      end
+
+      context 'with an expired license beyond the grace period' do
+        let(:expires_at) { Date.current - 15.days }
+        let(:block_changes_at) { Date.current - 1.day }
+
+        it 'notifies the admin of the expired subscription' do
+          expect(page).to have_content('Your subscription expired!')
+        end
+      end
+    end
+  end
+
+  context 'for namespace subscriptions', :saas do
+    let_it_be(:user) { create(:user) }
+    let_it_be(:group) { create(:group) }
+
+    before do
+      enable_namespace_license_check!
+
+      create(:gitlab_subscription, namespace: group, end_date: end_date, auto_renew: false)
+
+      allow_next_instance_of(GitlabSubscriptions::CheckFutureRenewalService, namespace: group) do |service|
+        allow(service).to receive(:execute).and_return(false)
+      end
+    end
+
+    context 'when signed in user is a group owner' do
+      before do
+        group.add_owner(user)
+
+        sign_in(user)
+        visit group_path(group)
+      end
+
+      context 'with an expired license' do
+        let(:end_date) { Date.current - 1.day }
+
+        it 'notifies the group owner of the expired subscription' do
+          expect(page).to have_content('Your subscription expired!')
+        end
+      end
+
+      context 'with a license expiring in less than 30 days' do
+        let(:end_date) { Date.current + 29.days }
+
+        it 'notifies the group owner of a soon expiring subscription' do
+          expect(page).to have_content('Your subscription will expire in 29 days')
+        end
+      end
+
+      context 'with a license expiring in 30 or more days' do
+        let(:end_date) { Date.current + 30.days }
+
+        include_examples 'no expiration notification'
+      end
+    end
+
+    context 'when signed in user is not a group owner' do
+      before do
+        group.add_developer(user)
+
+        sign_in(user)
+        visit group_path(group)
+      end
+
+      context 'with an expired license' do
+        let(:end_date) { Date.current - 1.day }
+
+        include_examples 'no expiration notification'
+      end
+
+      context 'with a license expiring in less than 30 days' do
+        let(:end_date) { Date.current + 29.days }
+
+        include_examples 'no expiration notification'
+      end
+    end
+  end
+end
diff --git a/ee/spec/models/license_spec.rb b/ee/spec/models/license_spec.rb
index bde0e16b5203b2e591307046a4e6f05b24c1f4af..08146a968d6fb31d8c32f0085026abf6123857f5 100644
--- a/ee/spec/models/license_spec.rb
+++ b/ee/spec/models/license_spec.rb
@@ -1623,4 +1623,151 @@ RSpec.describe License do
       it { is_expected.to eq(license.created_at) }
     end
   end
+
+  describe '#notify_admins?', :freeze_time do
+    subject(:notify_admins?) { license.notify_admins? }
+
+    context 'when license has expired' do
+      before do
+        gl_license.expires_at = Date.yesterday
+      end
+
+      it { is_expected.to eq(true) }
+    end
+
+    context 'when license has no expiration' do
+      before do
+        gl_license.expires_at = nil
+      end
+
+      it { is_expected.to eq(false) }
+    end
+
+    context 'when license has not expired' do
+      context 'when license is a trial' do
+        before do
+          gl_license.restrictions = { trial: true }
+        end
+
+        context 'when license expiration is more than a week from today' do
+          before do
+            gl_license.expires_at = Date.current + 8.days
+          end
+
+          it { is_expected.to eq(false) }
+        end
+
+        context 'when license expiration is a week from today' do
+          before do
+            gl_license.expires_at = Date.current + 7.days
+          end
+
+          it { is_expected.to eq(true) }
+        end
+
+        context 'when license expiration is less than a week from today' do
+          before do
+            gl_license.expires_at = Date.current + 6.days
+          end
+
+          it { is_expected.to eq(true) }
+        end
+      end
+
+      context 'when license is not a trial' do
+        context 'when license expiration is more than 15 days from today' do
+          before do
+            gl_license.expires_at = Date.current + 16.days
+          end
+
+          it { is_expected.to eq(false) }
+        end
+
+        context 'when license expiration is 15 days from today' do
+          before do
+            gl_license.expires_at = Date.current + 15.days
+          end
+
+          it { is_expected.to eq(true) }
+        end
+
+        context 'when license expiration is less than 15 days from today' do
+          before do
+            gl_license.expires_at = Date.current + 14.days
+          end
+
+          it { is_expected.to eq(true) }
+        end
+      end
+    end
+  end
+
+  describe '#notify_users?', :freeze_time do
+    subject(:notify_users?) { license.notify_users? }
+
+    context 'when license has no expiration' do
+      before do
+        gl_license.expires_at = nil
+        gl_license.block_changes_at = nil
+      end
+
+      it { is_expected.to eq(false) }
+    end
+
+    context 'when license is a trial' do
+      before do
+        gl_license.restrictions = { trial: true }
+      end
+
+      context 'when license expiration is more than a week from today' do
+        before do
+          gl_license.expires_at = Date.current + 8.days
+        end
+
+        it { is_expected.to eq(false) }
+      end
+
+      context 'when license expiration is a week from today' do
+        before do
+          gl_license.expires_at = Date.current + 7.days
+        end
+
+        it { is_expected.to eq(true) }
+      end
+
+      context 'when license expiration is less than a week from today' do
+        before do
+          gl_license.expires_at = Date.current + 6.days
+        end
+
+        it { is_expected.to eq(true) }
+      end
+    end
+
+    context 'when license is not a trial' do
+      context 'when license block changes date is before today' do
+        before do
+          gl_license.block_changes_at = Date.current - 1.day
+        end
+
+        it { is_expected.to eq(true) }
+      end
+
+      context 'when license block changes date is today' do
+        before do
+          gl_license.block_changes_at = Date.current
+        end
+
+        it { is_expected.to eq(true) }
+      end
+
+      context 'when license block changes date is after today' do
+        before do
+          gl_license.block_changes_at = Date.current + 1.day
+        end
+
+        it { is_expected.to eq(false) }
+      end
+    end
+  end
 end