diff --git a/.rubocop_todo/gitlab/bounded_contexts.yml b/.rubocop_todo/gitlab/bounded_contexts.yml
index 008a7bdf603af8506aba47c5f1703a0e9a826c2f..967fee319437fdea09e8ce27360f9972c6401991 100644
--- a/.rubocop_todo/gitlab/bounded_contexts.yml
+++ b/.rubocop_todo/gitlab/bounded_contexts.yml
@@ -4117,6 +4117,5 @@ Gitlab/BoundedContexts:
     - 'lib/unnested_in_filters/dsl.rb'
     - 'lib/unnested_in_filters/rewriter.rb'
     - 'lib/uploaded_file.rb'
-    - 'lib/version_check.rb'
     - 'lib/vite_gdk.rb'
     - 'lib/vs_code/settings.rb'
diff --git a/.rubocop_todo/gitlab/namespaced_class.yml b/.rubocop_todo/gitlab/namespaced_class.yml
index 6c5874ab069afc8b6770dc0b7fcb56dcb71323db..d1768b1c6debacf866d63b7325b5e6193430e091 100644
--- a/.rubocop_todo/gitlab/namespaced_class.yml
+++ b/.rubocop_todo/gitlab/namespaced_class.yml
@@ -1197,7 +1197,6 @@ Gitlab/NamespacedClass:
     - 'lib/tasks/gitlab/seed/group_seed.rake'
     - 'lib/tasks/tokens.rake'
     - 'lib/uploaded_file.rb'
-    - 'lib/version_check.rb'
     - 'spec/controllers/concerns/page_limiter_spec.rb'
     - 'spec/lib/bitbucket/collection_spec.rb'
     - 'spec/lib/gitlab/multi_destination_logger_spec.rb'
diff --git a/.rubocop_todo/lint/unused_block_argument.yml b/.rubocop_todo/lint/unused_block_argument.yml
index a6fec0306b67927f15aa904322e26b6bb3ea2393..373522011cfc1e5ea2bb11045306816b38b24c2e 100644
--- a/.rubocop_todo/lint/unused_block_argument.yml
+++ b/.rubocop_todo/lint/unused_block_argument.yml
@@ -228,7 +228,6 @@ Lint/UnusedBlockArgument:
     - 'lib/tasks/gitlab/uploads/sanitize.rake'
     - 'lib/tasks/gitlab/user_management.rake'
     - 'lib/tasks/gitlab/workhorse.rake'
-    - 'lib/version_check.rb'
     - 'qa/qa/service/praefect_manager.rb'
     - 'qa/qa/specs/features/api/12_systems/gitaly/distributed_reads_spec.rb'
     - 'qa/qa/support/matchers/eventually_matcher.rb'
diff --git a/.rubocop_todo/rspec/context_wording.yml b/.rubocop_todo/rspec/context_wording.yml
index 8b9cbddb6692681bad4ab8bcaeb55732ab8c479d..9f2d6559e8185dee5c66701cf87e76f1779ca040 100644
--- a/.rubocop_todo/rspec/context_wording.yml
+++ b/.rubocop_todo/rspec/context_wording.yml
@@ -1862,7 +1862,6 @@ RSpec/ContextWording:
     - 'spec/lib/system_check/base_check_spec.rb'
     - 'spec/lib/system_check/incoming_email/imap_authentication_check_spec.rb'
     - 'spec/lib/uploaded_file_spec.rb'
-    - 'spec/lib/version_check_spec.rb'
     - 'spec/mailers/devise_mailer_spec.rb'
     - 'spec/mailers/emails/profile_spec.rb'
     - 'spec/mailers/emails/projects_spec.rb'
diff --git a/.rubocop_todo/rspec/feature_category.yml b/.rubocop_todo/rspec/feature_category.yml
index c6dc02a81510fcf6d6172c3668bea57f2d9943f2..cb898b6ed3d6ed89b071a8e3177fc74de263c5fd 100644
--- a/.rubocop_todo/rspec/feature_category.yml
+++ b/.rubocop_todo/rspec/feature_category.yml
@@ -3099,7 +3099,6 @@ RSpec/FeatureCategory:
     - 'spec/lib/system_check/simple_executor_spec.rb'
     - 'spec/lib/system_check_spec.rb'
     - 'spec/lib/unnested_in_filters/dsl_spec.rb'
-    - 'spec/lib/version_check_spec.rb'
     - 'spec/mailers/abuse_report_mailer_spec.rb'
     - 'spec/mailers/email_rejection_mailer_spec.rb'
     - 'spec/mailers/emails/admin_notification_spec.rb'
diff --git a/app/controllers/admin/version_check_controller.rb b/app/controllers/admin/version_check_controller.rb
index f5c70dc9e1b9de7d32a1965074db5b774f190de1..33cedce7ee536fdbb208b6469297ea468a3b65ee 100644
--- a/app/controllers/admin/version_check_controller.rb
+++ b/app/controllers/admin/version_check_controller.rb
@@ -3,8 +3,10 @@
 class Admin::VersionCheckController < Admin::ApplicationController
   feature_category :not_owned # rubocop:todo Gitlab/AvoidFeatureCategoryNotOwned
 
+  include VersionCheckHelper
+
   def version_check
-    response = VersionCheck.new.response
+    response = gitlab_version_check
 
     expires_in 1.minute if response
     render json: response
diff --git a/app/helpers/version_check_helper.rb b/app/helpers/version_check_helper.rb
index 76404862b960d0d2b33a9c4819218eb364efce12..ac6b20de8a5f7acf8bc5d2bb01ee1d765f1de0c5 100644
--- a/app/helpers/version_check_helper.rb
+++ b/app/helpers/version_check_helper.rb
@@ -12,7 +12,10 @@ def show_version_check?
   def gitlab_version_check
     return unless show_version_check?
 
-    VersionCheck.new.response
+    version = Rails.cache.fetch('version_check')
+    Gitlab::Version::VersionCheckCronWorker.perform_async if version.nil?
+
+    version
   end
   strong_memoize_attr :gitlab_version_check
 
diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml
index f98e31efe9b2f4b8c2a3a48cb37fcb160c8c256a..0cdec4ed74cd5a52c5c95b0a2e605de67991abbc 100644
--- a/app/workers/all_queues.yml
+++ b/app/workers/all_queues.yml
@@ -1166,6 +1166,16 @@
   :idempotent: true
   :tags: []
   :queue_namespace: :cronjob
+- :name: cronjob:version_version_check_cron
+  :worker_name: Gitlab::Version::VersionCheckCronWorker
+  :feature_category: :service_ping
+  :has_external_dependencies: false
+  :urgency: :low
+  :resource_boundary: :unknown
+  :weight: 1
+  :idempotent: true
+  :tags: []
+  :queue_namespace: :cronjob
 - :name: cronjob:x509_issuer_crl_check
   :worker_name: X509IssuerCrlCheckWorker
   :feature_category: :source_code_management
diff --git a/app/workers/gitlab/version/version_check_cron_worker.rb b/app/workers/gitlab/version/version_check_cron_worker.rb
new file mode 100644
index 0000000000000000000000000000000000000000..cf020775433f848451c4522214b6b6850d681f7b
--- /dev/null
+++ b/app/workers/gitlab/version/version_check_cron_worker.rb
@@ -0,0 +1,55 @@
+# frozen_string_literal: true
+
+require "base64"
+
+module Gitlab
+  module Version
+    class VersionCheckCronWorker
+      include ApplicationWorker
+      include CronjobQueue # rubocop: disable Scalability/CronWorkerContext -- no relevant metadata
+
+      deduplicate :until_executed
+      idempotent!
+
+      data_consistency :sticky
+
+      sidekiq_options retry: 3
+
+      feature_category :service_ping
+      urgency :low
+
+      def perform
+        response = Gitlab::HTTP.try_get(url)
+
+        if response.present? && response.code == 200
+          result = Gitlab::Json.parse(response.body)
+          Gitlab::AppLogger.info(message: 'Version check succeeded', result: result)
+
+          Rails.cache.write("version_check", result)
+        else
+          Gitlab::AppLogger.error(message: 'Version check failed',
+            error: { code: response&.code, message: response&.body })
+        end
+      rescue JSON::ParserError => e
+        Gitlab::AppLogger.error(message: 'Parsing version check response failed',
+          error: { message: e.message, code: response&.code })
+      end
+
+      private
+
+      def data
+        { version: Gitlab::VERSION }
+      end
+
+      def url
+        encoded_data = Base64.urlsafe_encode64(data.to_json)
+
+        "#{host}/check.json?gitlab_info=#{encoded_data}"
+      end
+
+      def host
+        'https://version.gitlab.com'
+      end
+    end
+  end
+end
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index 8a02b72906abd3a7fcd7df58de5ca53558e1634f..51f80a6fc4d42778c1b8051c8b9e5effbbcb5769 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -741,6 +741,9 @@
 Settings.cron_jobs['ci_schedule_old_pipelines_removal_cron_worker'] ||= {}
 Settings.cron_jobs['ci_schedule_old_pipelines_removal_cron_worker']['cron'] ||= '*/11 * * * *'
 Settings.cron_jobs['ci_schedule_old_pipelines_removal_cron_worker']['job_class'] = 'Ci::ScheduleOldPipelinesRemovalCronWorker'
+Settings.cron_jobs['version_version_check_cron'] ||= {}
+Settings.cron_jobs['version_version_check_cron']['cron'] ||= "#{rand(60)} #{rand(24)} * * *"
+Settings.cron_jobs['version_version_check_cron']['job_class'] = 'Gitlab::Version::VersionCheckCronWorker'
 
 Gitlab.ee do
   Settings.cron_jobs['analytics_devops_adoption_create_all_snapshots_worker'] ||= {}
diff --git a/ee/spec/features/admin/admin_dashboard_spec.rb b/ee/spec/features/admin/admin_dashboard_spec.rb
index 4afc70da1ea42378fcb474c9d264bead94537509..8b9d54faee73da7d18a9286186186f60ff338966 100644
--- a/ee/spec/features/admin/admin_dashboard_spec.rb
+++ b/ee/spec/features/admin/admin_dashboard_spec.rb
@@ -3,6 +3,8 @@
 require 'spec_helper'
 
 RSpec.describe 'Admin Dashboard', feature_category: :shared do
+  include VersionCheckHelpers
+
   before do
     admin = create(:admin)
     sign_in(admin)
@@ -101,6 +103,10 @@
   end
 
   describe 'Version check', :js do
+    before do
+      stub_version_check({ "severity" => "success" })
+    end
+
     it 'shows badge on EE' do
       visit admin_root_path
 
diff --git a/lib/version_check.rb b/lib/version_check.rb
deleted file mode 100644
index 1447a44f6e36d5883094a0500d0775caa6f661be..0000000000000000000000000000000000000000
--- a/lib/version_check.rb
+++ /dev/null
@@ -1,88 +0,0 @@
-# frozen_string_literal: true
-
-require "base64"
-
-class VersionCheck
-  include ReactiveCaching
-
-  # Increment when format of cache value is changed
-  CACHE_VERSION = 1
-
-  ## Version Check Reactive Caching
-  ## This cache stores the external API response from https://version.gitlab.com
-  ##
-  ## Example API Response
-  ## {
-  ##   "latest_version": "15.2.2",
-  ##   "severity": "success"
-  ## }
-  ##
-  ## This response from this endpoint only changes in 2 scenarios:
-  ## 1. Customer upgrades their GitLab Instance
-  ## 2. GitLab releases a new version
-  ##
-  ## We use GitLab::VERSION as the identifier for the cached information.
-  ## This means if the user upgrades their version we will create a new cache record.
-  ## The old one will be invalidated and cleaned up at the end of the self.reactive_cache_lifetime.
-  ##
-  ## - self.reactive_cache_refresh_interval = 12.hours
-  ## We want to prevent as many external API calls as possible to save on resources.
-  ## Since an EXISTING cache record will only become "invalid" if GitLab releases a new version we
-  ## determined that 12 hour intervals is enough of a window to capture an available upgrade.
-  ##
-  ## - self.reactive_cache_lifetime = 7.days
-  ## We don't want the data to be missing every time a user revisits a page using this info.
-  ## Thus 7 days seems like a fair amount of time before we erase the cache.
-  ## This also will handle cleaning up old cache records as they will no longer be accessed after an upgrade.
-  ##
-
-  self.reactive_cache_refresh_interval = 12.hours
-  self.reactive_cache_lifetime = 7.days
-  self.reactive_cache_work_type = :external_dependency
-  self.reactive_cache_worker_finder = ->(_id, *args) { from_cache }
-
-  def self.data
-    { version: Gitlab::VERSION }
-  end
-
-  def self.url
-    encoded_data = Base64.urlsafe_encode64(data.to_json)
-
-    "#{host}/check.json?gitlab_info=#{encoded_data}"
-  end
-
-  def self.host
-    'https://version.gitlab.com'
-  end
-
-  def self.from_cache(*)
-    new
-  end
-
-  def id
-    [Gitlab::VERSION, Gitlab.revision, CACHE_VERSION].join('-')
-  end
-
-  def calculate_reactive_cache(*)
-    response = Gitlab::HTTP.try_get(self.class.url)
-
-    case response&.code
-    when 200
-      Gitlab::Json.parse(response.body)
-    else
-      { error: 'version check failed', status: response&.code }
-    end
-  rescue JSON::ParserError
-    { error: 'parsing version check response failed', status: response&.code }
-  end
-
-  def response
-    with_reactive_cache do |data|
-      raise InvalidateReactiveCache if !data.is_a?(Hash) || data[:error]
-
-      data
-    end
-  end
-end
-
-VersionCheck.prepend_mod
diff --git a/spec/features/admin/dashboard_spec.rb b/spec/features/admin/dashboard_spec.rb
index 67e7cd6dc94c3c92fb247a3cc320fc159bd75fdf..da2e50651a9a9922a387257d0e8b2b50adaf0e8d 100644
--- a/spec/features/admin/dashboard_spec.rb
+++ b/spec/features/admin/dashboard_spec.rb
@@ -4,6 +4,7 @@
 
 RSpec.describe 'admin visits dashboard' do
   include ProjectForksHelper
+  include VersionCheckHelpers
 
   before do
     admin = create(:admin)
@@ -55,6 +56,10 @@
   end
 
   describe 'Version check', :js, feature_category: :deployment_management do
+    before do
+      stub_version_check({ "severity" => "success" })
+    end
+
     it 'shows badge on CE' do
       visit admin_root_path
 
diff --git a/spec/features/help_dropdown_spec.rb b/spec/features/help_dropdown_spec.rb
index e9ef67a70c34a7f0982f978715646bad6dc6fa64..95b879a808cef8eea8521680ab2b9eebd9cb7d33 100644
--- a/spec/features/help_dropdown_spec.rb
+++ b/spec/features/help_dropdown_spec.rb
@@ -3,6 +3,8 @@
 require 'spec_helper'
 
 RSpec.describe "Help Dropdown", :js, feature_category: :shared do
+  include VersionCheckHelpers
+
   let_it_be(:user) { create(:user) }
   let_it_be(:admin) { create(:admin) }
 
@@ -29,9 +31,7 @@
         sign_in(admin)
         enable_admin_mode!(admin)
 
-        allow_next_instance_of(VersionCheck) do |instance|
-          allow(instance).to receive(:response).and_return({ "severity" => severity })
-        end
+        stub_version_check({ "severity" => severity })
         visit root_path
       end
 
diff --git a/spec/helpers/version_check_helper_spec.rb b/spec/helpers/version_check_helper_spec.rb
index 9af903065b3bf30ee8e96fb4dbb06d04f5af29af..3bf332927d1d8bbf99e2fbd06380ea5b59faa94c 100644
--- a/spec/helpers/version_check_helper_spec.rb
+++ b/spec/helpers/version_check_helper_spec.rb
@@ -51,14 +51,32 @@
     context 'when show_version_check? is true' do
       let(:show_version_check) { true }
 
-      before do
-        allow_next_instance_of(VersionCheck) do |instance|
-          allow(instance).to receive(:response).and_return({ "severity" => "success" })
+      context 'when it has no cached version_check response' do
+        before do
+          allow(Rails.cache).to receive(:fetch).with('version_check').and_return(nil)
+        end
+
+        it 'schedules a version check worker' do
+          expect(Gitlab::Version::VersionCheckCronWorker).to receive(:perform_async)
+
+          helper.gitlab_version_check
+        end
+
+        it 'returns nil' do
+          expect(helper.gitlab_version_check).to be nil
         end
       end
 
-      it 'returns an instance of the VersionCheck class if the user has access' do
-        expect(helper.gitlab_version_check).to eq({ "severity" => "success" })
+      context 'when it has a cached version_check response' do
+        let(:version_check) { { "severity" => "success" } }
+
+        before do
+          allow(Rails.cache).to receive(:fetch).with('version_check').and_return(version_check)
+        end
+
+        it 'returns the cached version check response' do
+          expect(helper.gitlab_version_check).to eq(version_check)
+        end
       end
     end
   end
diff --git a/spec/lib/version_check_spec.rb b/spec/lib/version_check_spec.rb
deleted file mode 100644
index 4aa8975b7cf81ba20fe8ce6483804be1a241fdcb..0000000000000000000000000000000000000000
--- a/spec/lib/version_check_spec.rb
+++ /dev/null
@@ -1,114 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe VersionCheck, :use_clean_rails_memory_store_caching do
-  include ReactiveCachingHelpers
-
-  describe '.url' do
-    it 'returns the correct URL' do
-      expect(described_class.url).to match(%r{\A#{Regexp.escape(described_class.host)}/check\.json\?gitlab_info=\w+})
-    end
-  end
-
-  context 'reactive cache properties' do
-    describe '.reactive_cache_refresh_interval' do
-      it 'returns 12.hours' do
-        expect(described_class.reactive_cache_refresh_interval).to eq(12.hours)
-      end
-    end
-
-    describe '.reactive_cache_lifetime' do
-      it 'returns 7.days' do
-        expect(described_class.reactive_cache_lifetime).to eq(7.days)
-      end
-    end
-  end
-
-  describe '#calculate_reactive_cache' do
-    context 'response code is 200 with valid body' do
-      before do
-        stub_request(:get, described_class.url).to_return(status: 200, body: '{ "status": "success" }', headers: {})
-      end
-
-      it 'returns the response object' do
-        expect(described_class.new.calculate_reactive_cache).to eq({ "status" => "success" })
-      end
-    end
-
-    context 'response code is 200 with invalid body' do
-      before do
-        stub_request(:get, described_class.url).to_return(status: 200, body: '{ "invalid: json" }', headers: {})
-      end
-
-      it 'returns an error hash' do
-        expect(described_class.new.calculate_reactive_cache).to eq(
-          { error: 'parsing version check response failed', status: 200 }
-        )
-      end
-    end
-
-    context 'response code is not 200' do
-      before do
-        stub_request(:get, described_class.url).to_return(status: 500, body: nil, headers: {})
-      end
-
-      it 'returns an error hash' do
-        expect(described_class.new.calculate_reactive_cache).to eq({ error: 'version check failed', status: 500 })
-      end
-    end
-  end
-
-  describe '#response' do
-    # see https://gitlab.com/gitlab-org/gitlab/-/merge_requests/106254
-    context "with old string value in cache" do
-      before do
-        old_version_check = described_class.new
-        allow(old_version_check).to receive(:id).and_return(Gitlab::VERSION)
-        write_reactive_cache(old_version_check,
-          "{\"latest_stable_versions\":[],\"latest_version\":\"15.6.2\",\"severity\":\"success\",\"details\":\"\"}"
-        )
-      end
-
-      it 'returns nil' do
-        version_check = described_class.new
-        expect(version_check.response).to be_nil
-      end
-    end
-
-    # see https://gitlab.com/gitlab-org/gitlab/-/merge_requests/106254
-    context "with non-hash value in cache" do
-      it 'returns nil and invalidates the reactive cache' do
-        version_check = described_class.new
-        stub_reactive_cache(version_check,
-          "{\"latest_stable_versions\":[],\"latest_version\":\"15.6.2\",\"severity\":\"success\",\"details\":\"\"}"
-        )
-
-        expect(version_check).to receive(:refresh_reactive_cache!).and_call_original
-        expect(version_check.response).to be_nil
-        expect(read_reactive_cache(version_check)).to be_nil
-      end
-    end
-
-    context 'cache returns value' do
-      it 'returns the response object' do
-        version_check = described_class.new
-        data = { status: 'success' }
-        stub_reactive_cache(version_check, data)
-
-        expect(version_check.response).to eq(data)
-      end
-    end
-
-    context 'cache returns error' do
-      it 'returns nil and invalidates the reactive cache' do
-        version_check = described_class.new
-        stub_reactive_cache(version_check, error: 'version check failed')
-
-        expect(version_check).to receive(:refresh_reactive_cache!).and_call_original
-        expect(version_check.response).to be_nil
-        expect(read_reactive_cache(version_check)).to be_nil
-      end
-    end
-  end
-end
diff --git a/spec/requests/admin/version_check_controller_spec.rb b/spec/requests/admin/version_check_controller_spec.rb
index a998c2f426b59c76b857cd0e3164c707b5b4424f..291b17284df6633b584441aaaffe9dee0ab33b6d 100644
--- a/spec/requests/admin/version_check_controller_spec.rb
+++ b/spec/requests/admin/version_check_controller_spec.rb
@@ -10,40 +10,38 @@
   end
 
   describe 'GET #version_check' do
-    context 'when VersionCheck.response is nil' do
+    let(:version_check_response) { { 'success' => true, 'version' => '16.0.0' } }
+
+    context 'when version check is successful' do
       before do
-        allow_next_instance_of(VersionCheck) do |instance|
-          allow(instance).to receive(:response).and_return(nil)
-        end
-        get admin_version_check_path
+        allow(Rails.cache).to receive(:fetch).with("version_check").and_return(version_check_response)
       end
 
-      it 'returns nil' do
+      it 'returns version check data' do
+        get admin_version_check_path
+
         expect(response).to have_gitlab_http_status(:ok)
-        expect(json_response).to be_nil
+        expect(json_response).to eq(version_check_response)
       end
 
-      it 'sets no-cache headers' do
-        expect(response.headers['Cache-Control']).to eq('max-age=0, private, must-revalidate')
+      it 'sets cache expiration to 1 minute' do
+        get admin_version_check_path
+
+        expect(response.headers['Cache-Control']).to include('max-age=60')
       end
     end
 
-    context 'when VersionCheck.response is valid' do
+    context 'when version check fails' do
       before do
-        allow_next_instance_of(VersionCheck) do |instance|
-          allow(instance).to receive(:response).and_return({ "severity" => "success" })
-        end
+        allow(VersionCheckHelper).to receive(:gitlab_version_check).and_return(nil)
+      end
 
+      it 'returns nil without cache headers' do
         get admin_version_check_path
-      end
 
-      it 'returns the valid data' do
         expect(response).to have_gitlab_http_status(:ok)
-        expect(json_response).to eq({ "severity" => "success" })
-      end
-
-      it 'sets proper cache headers' do
-        expect(response.headers['Cache-Control']).to eq('max-age=60, private')
+        expect(json_response).to be_nil
+        expect(response.headers['Cache-Control']).not_to include('max-age=60')
       end
     end
   end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 4aa26e423f7c8148095aa452953ceae2ee66338c..acb698e37593ee96b00a4826f112ca33da3d0d53 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -224,6 +224,7 @@
   include StubFeatureFlags
   include StubSnowplow
   include StubMember
+  include VersionCheckHelpers
 
   if ENV['CI'] || ENV['RETRIES']
     # Gradually stop using rspec-retry
@@ -497,7 +498,7 @@
 
   # Ensures that any Javascript script that tries to make the external VersionCheck API call skips it and returns a response
   config.before(:each, :js) do
-    allow_any_instance_of(VersionCheck).to receive(:response).and_return({ "severity" => "success" })
+    stub_version_check({ "severity" => "success" })
   end
 
   [:migration, :delete].each do |spec_type|
diff --git a/spec/support/helpers/version_check_helpers.rb b/spec/support/helpers/version_check_helpers.rb
new file mode 100644
index 0000000000000000000000000000000000000000..caf8c15036798b516323d9b24fa3f64f51c657a9
--- /dev/null
+++ b/spec/support/helpers/version_check_helpers.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+
+module VersionCheckHelpers
+  def stub_version_check(response)
+    allow(Rails.cache).to receive(:fetch).and_call_original
+    allow(Rails.cache).to receive(:fetch).with('version_check').and_return(response)
+  end
+end
diff --git a/spec/support/rspec_order_todo.yml b/spec/support/rspec_order_todo.yml
index f363f8fee77b34ecb7356ef8ecdfed84dc80bd28..9d11e8be59e5e5f104e3d9cd49d202ac00ff7151 100644
--- a/spec/support/rspec_order_todo.yml
+++ b/spec/support/rspec_order_todo.yml
@@ -5745,7 +5745,6 @@
 - './spec/lib/unnested_in_filters/dsl_spec.rb'
 - './spec/lib/unnested_in_filters/rewriter_spec.rb'
 - './spec/lib/uploaded_file_spec.rb'
-- './spec/lib/version_check_spec.rb'
 - './spec/mailers/abuse_report_mailer_spec.rb'
 - './spec/mailers/devise_mailer_spec.rb'
 - './spec/mailers/email_rejection_mailer_spec.rb'
diff --git a/spec/views/shared/gitlab_version/_security_patch_upgrade_alert.html.haml_spec.rb b/spec/views/shared/gitlab_version/_security_patch_upgrade_alert.html.haml_spec.rb
index a0e2e5507550d195016595a01a16d36cb5cadb0a..82b180a36199893e1be73e6ffadade042c504b07 100644
--- a/spec/views/shared/gitlab_version/_security_patch_upgrade_alert.html.haml_spec.rb
+++ b/spec/views/shared/gitlab_version/_security_patch_upgrade_alert.html.haml_spec.rb
@@ -3,15 +3,13 @@
 require 'spec_helper'
 
 RSpec.describe 'shared/gitlab_version/_security_patch_upgrade_alert' do
+  include VersionCheckHelper
+
   let_it_be(:user) { build_stubbed(:user) }
-  let(:version_check_response) { { 'critical_vulnerability' => 'true' } }
 
   before do
     stub_application_setting(version_check_enabled: true)
-
-    allow_next_instance_of(VersionCheck) do |service|
-      allow(service).to receive(:response).and_return(version_check_response)
-    end
+    stub_version_check({ 'critical_vulnerability' => 'true' })
   end
 
   describe 'when version check is enabled and is admin' do
diff --git a/spec/workers/every_sidekiq_worker_spec.rb b/spec/workers/every_sidekiq_worker_spec.rb
index 1b9a0db11031ee8a65223969ee60d52a370f169c..794675b81224489c7844e0f6946ac0144c3809cc 100644
--- a/spec/workers/every_sidekiq_worker_spec.rb
+++ b/spec/workers/every_sidekiq_worker_spec.rb
@@ -311,6 +311,7 @@
         'Gitlab::JiraImport::Stage::ImportLabelsWorker' => 6,
         'Gitlab::JiraImport::Stage::ImportNotesWorker' => 6,
         'Gitlab::JiraImport::Stage::StartImportWorker' => 6,
+        'Gitlab::Version::VersionCheckCronWorker' => 3,
         'GitlabPerformanceBarStatsWorker' => 3,
         'GitlabSubscriptions::RefreshSeatsWorker' => 0,
         'GitlabSubscriptions::AddOnPurchases::BulkRefreshUserAssignmentsWorker' => 0,
diff --git a/spec/workers/gitlab/version/version_check_cron_worker_spec.rb b/spec/workers/gitlab/version/version_check_cron_worker_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..d07fd89433d38f2c60e8f6fab159dd120d4e9fca
--- /dev/null
+++ b/spec/workers/gitlab/version/version_check_cron_worker_spec.rb
@@ -0,0 +1,82 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Version::VersionCheckCronWorker, feature_category: :service_ping do
+  let(:worker) { described_class.new }
+  let(:response) { double }
+  let(:version_info) { { 'version' => '1.0.0' } }
+  let(:encoded_data) { Base64.urlsafe_encode64({ version: Gitlab::VERSION }.to_json) }
+  let(:version_url) { "https://version.gitlab.com/check.json?gitlab_info=#{encoded_data}" }
+
+  before do
+    allow(Gitlab::HTTP).to receive(:try_get).with(version_url).and_return(response)
+  end
+
+  describe '#perform' do
+    context 'when request is successful' do
+      before do
+        allow(response).to receive_messages(body: version_info.to_json, code: 200)
+      end
+
+      it 'caches the version information' do
+        expect(Rails.cache).to receive(:write).with('version_check', version_info)
+
+        worker.perform
+      end
+
+      it 'logs the response' do
+        expect(Gitlab::AppLogger).to receive(:info).with(
+          message: 'Version check succeeded',
+          result: version_info)
+
+        worker.perform
+      end
+    end
+
+    context 'when request fails' do
+      before do
+        allow(response).to receive_messages(body: 'error', code: 500)
+      end
+
+      it 'logs an error' do
+        expect(Gitlab::AppLogger).to receive(:error).with(
+          message: 'Version check failed',
+          error: { code: 500, message: 'error' }
+        )
+
+        worker.perform
+      end
+    end
+
+    context 'when response is not present' do
+      before do
+        allow(Gitlab::HTTP).to receive(:try_get).with(version_url).and_return(nil)
+      end
+
+      it 'logs an error' do
+        expect(Gitlab::AppLogger).to receive(:error).with(
+          message: 'Version check failed',
+          error: { code: nil, message: nil }
+        )
+
+        worker.perform
+      end
+    end
+
+    context 'when JSON parsing fails' do
+      before do
+        allow(response).to receive_messages(body: 'invalid json', code: 200)
+      end
+
+      it 'logs a parsing error' do
+        expect(Gitlab::AppLogger).to receive(:error).with(
+          message: 'Parsing version check response failed',
+          error: { message: kind_of(String), code: 200 }
+        )
+
+        worker.perform
+      end
+    end
+  end
+end