diff --git a/ee/app/policies/ee/global_policy.rb b/ee/app/policies/ee/global_policy.rb
index d0b23811f2b000f0696b4fa335ed7bbdadde54de..3e9396cdcfe91bbc1acfae01975067dce4632403 100644
--- a/ee/app/policies/ee/global_policy.rb
+++ b/ee/app/policies/ee/global_policy.rb
@@ -73,7 +73,9 @@ module GlobalPolicy
         next true if ::Gitlab::Saas.feature_available?(:duo_chat_on_saas)
         next false unless ::License.feature_available?(:ai_chat)
 
-        if duo_chat_free_access_was_cut_off?
+        if duo_chat_self_hosted?
+          self_hosted_models_available_for?(@user)
+        elsif duo_chat_free_access_was_cut_off?
           duo_chat.allowed_for?(@user)
         else # Before service start date
           ::Gitlab::CurrentSettings.duo_features_enabled?
@@ -84,7 +86,9 @@ module GlobalPolicy
         next false unless @user
         next true unless ::Gitlab::Saas.feature_available?(:duo_chat_on_saas)
 
-        if duo_chat_free_access_was_cut_off?
+        if duo_chat_self_hosted?
+          self_hosted_models_available_for?(@user)
+        elsif duo_chat_free_access_was_cut_off?
           duo_chat.allowed_for?(@user)
         else
           @user.any_group_with_ai_chat_available?
@@ -217,5 +221,17 @@ def duo_chat_free_access_was_cut_off_for_sm?
     def duo_chat
       CloudConnector::AvailableServices.find_by_name(:duo_chat)
     end
+
+    # Check whether a user is allowed to use Duo Chat powered by self-hosted models
+    def duo_chat_self_hosted?
+      ::Ai::FeatureSetting.find_by_feature(:duo_chat)&.self_hosted?
+    end
+
+    def self_hosted_models_available_for?(user)
+      service = CloudConnector::AvailableServices.find_by_name(:self_hosted_models)
+      return false unless service
+
+      service.free_access? || service.allowed_for?(user)
+    end
   end
 end
diff --git a/ee/lib/gitlab/llm/ai_gateway/docs_client.rb b/ee/lib/gitlab/llm/ai_gateway/docs_client.rb
index ab96c2397d7892753ed7162703573af951ff4264..7c2ae2275d5985a5c73e8de33a4e6497466bcbf2 100644
--- a/ee/lib/gitlab/llm/ai_gateway/docs_client.rb
+++ b/ee/lib/gitlab/llm/ai_gateway/docs_client.rb
@@ -50,7 +50,10 @@ def enabled?
         end
 
         def access_token
-          ::CloudConnector::AvailableServices.find_by_name(:duo_chat).access_token(user)
+          chat_feature_setting = ::Ai::FeatureSetting.find_by_feature(:duo_chat)
+          feature_name = chat_feature_setting&.self_hosted? ? :self_hosted_models : :duo_chat
+
+          ::CloudConnector::AvailableServices.find_by_name(feature_name).access_token(user)
         end
         strong_memoize_attr :access_token
 
diff --git a/ee/lib/gitlab/llm/chain/requests/ai_gateway.rb b/ee/lib/gitlab/llm/chain/requests/ai_gateway.rb
index 97e8a82692613a8217b1fa3bc23f09f45c7f4d18..055501e0eaf4173219ae14ef083fc37cc234ef16 100644
--- a/ee/lib/gitlab/llm/chain/requests/ai_gateway.rb
+++ b/ee/lib/gitlab/llm/chain/requests/ai_gateway.rb
@@ -25,7 +25,7 @@ class AiGateway < Base
           def initialize(user, service_name: :duo_chat, tracking_context: {})
             @user = user
             @tracking_context = tracking_context
-            @ai_client = ::Gitlab::Llm::AiGateway::Client.new(user, service_name: service_name,
+            @ai_client = ::Gitlab::Llm::AiGateway::Client.new(user, service_name: processed_service_name(service_name),
               tracking_context: tracking_context)
             @logger = Gitlab::Llm::Logger.build
           end
@@ -171,6 +171,13 @@ def tracking_class_name(provider)
           def chat_feature_setting
             ::Ai::FeatureSetting.find_by_feature(:duo_chat)
           end
+
+          def processed_service_name(service_name)
+            return service_name unless service_name == :duo_chat
+            return service_name unless chat_feature_setting&.self_hosted?
+
+            :self_hosted_models
+          end
         end
       end
     end
diff --git a/ee/spec/lib/gitlab/llm/ai_gateway/docs_client_spec.rb b/ee/spec/lib/gitlab/llm/ai_gateway/docs_client_spec.rb
index 4b6220eecea6c5845dfa2e48ffafc0e3c9a1629e..3a15540e7a4c7c3e7c41957fe2a057276c677193 100644
--- a/ee/spec/lib/gitlab/llm/ai_gateway/docs_client_spec.rb
+++ b/ee/spec/lib/gitlab/llm/ai_gateway/docs_client_spec.rb
@@ -90,5 +90,22 @@
         expect(result).to eq(nil)
       end
     end
+
+    context 'when duo chat model is self-hosted' do
+      let_it_be(:feature_setting) { create(:ai_feature_setting, feature: :duo_chat) }
+
+      it 'returns access token for self-hosted-models service' do
+        service = instance_double('::CloudConnector::SelfSigned::AvailableServiceData')
+        expect(::CloudConnector::AvailableServices).to receive(:find_by_name)
+          .with(:self_hosted_models).and_return(service)
+        allow(service).to receive(:access_token).and_return(expected_access_token)
+
+        expect(Gitlab::HTTP).to receive(:post).with(
+          anything,
+          hash_including(timeout: described_class::DEFAULT_TIMEOUT)
+        ).and_call_original
+        expect(result.parsed_response).to eq(expected_response)
+      end
+    end
   end
 end
diff --git a/ee/spec/lib/gitlab/llm/chain/requests/ai_gateway_spec.rb b/ee/spec/lib/gitlab/llm/chain/requests/ai_gateway_spec.rb
index 7818ac61e45a6894720ed9f868ec687ec6ea22e1..cb0881c20d901c19ec2567f6f68f88f92d4b8265 100644
--- a/ee/spec/lib/gitlab/llm/chain/requests/ai_gateway_spec.rb
+++ b/ee/spec/lib/gitlab/llm/chain/requests/ai_gateway_spec.rb
@@ -24,6 +24,20 @@
         described_class.new(user, service_name: :alternative, tracking_context: tracking_context)
       end
     end
+
+    context 'when duo chat is self-hosted' do
+      let_it_be(:feature_setting) { create(:ai_feature_setting, feature: :duo_chat, provider: :self_hosted) }
+
+      it 'creates ai gateway client with self-hosted-models service name' do
+        expect(::Gitlab::Llm::AiGateway::Client).to receive(:new).with(
+          user,
+          service_name: :self_hosted_models,
+          tracking_context: tracking_context
+        )
+
+        described_class.new(user, service_name: :duo_chat, tracking_context: tracking_context)
+      end
+    end
   end
 
   describe '#request' do
diff --git a/ee/spec/policies/global_policy_spec.rb b/ee/spec/policies/global_policy_spec.rb
index 208d418b62699f3af5a313bf79152b4ef296d8ee..b6b82283277e942d208d27b814d1c3ddeb68af2e 100644
--- a/ee/spec/policies/global_policy_spec.rb
+++ b/ee/spec/policies/global_policy_spec.rb
@@ -641,15 +641,19 @@
 
     context 'when on .org or .com', :saas do
       where(:group_with_ai_membership, :duo_pro_seat_assigned, :requires_licensed_seat,
-        :duo_chat_enabled_for_user) do
-        false | false  | false | be_disallowed(policy)
-        false | true   | false | be_disallowed(policy)
-        true  | false  | false | be_allowed(policy)
-        true  | true   | false | be_allowed(policy)
+        :self_hosted_duo_chat_enabled, :self_hosted_duo_chat_available, :duo_chat_enabled_for_user) do
+        false | false  | false | false | false | be_disallowed(policy)
+        false | true   | false | false | false | be_disallowed(policy)
+        true  | false  | false | false | false | be_allowed(policy)
+        true  | true   | false | false | false | be_allowed(policy)
 
         # When Group actor belongs to a group which requires licensed seat for chat
-        true  | false  | true | be_disallowed(policy)
-        true  | true   | true | be_allowed(policy)
+        true  | false  | true  | false | false | be_disallowed(policy)
+        true  | true   | true  | false | false | be_allowed(policy)
+
+        # When Duo chat is self-hosted
+        false | false  | false | true  | false | be_disallowed(policy)
+        false | false  | false | true  | true  | be_allowed(policy)
       end
 
       with_them do
@@ -661,6 +665,16 @@
           allow(duo_chat_service_data).to receive(:allowed_for?).with(current_user).and_return(duo_pro_seat_assigned)
           allow(current_user).to receive(:belongs_to_group_requires_licensed_seat_for_chat?)
                                    .and_return(requires_licensed_seat)
+
+          allow(::Ai::FeatureSetting).to receive_message_chain(:find_by_feature,
+            :self_hosted?).and_return(self_hosted_duo_chat_enabled)
+          self_hosted_service_data = instance_double(CloudConnector::SelfSigned::AvailableServiceData)
+          allow(CloudConnector::AvailableServices).to receive(:find_by_name).with(:self_hosted_models)
+                                                                            .and_return(self_hosted_service_data)
+          allow(self_hosted_service_data).to receive(:allowed_for?)
+            .with(current_user).and_return(self_hosted_duo_chat_available)
+          allow(self_hosted_service_data).to receive(:free_access?)
+            .and_return(self_hosted_duo_chat_available)
         end
 
         it { is_expected.to duo_chat_enabled_for_user }
@@ -672,19 +686,22 @@
       let_it_be(:yesterday) { Time.current - 1.day }
 
       where(:licensed, :duo_features_enabled, :duo_chat_cut_off_date, :duo_pro_seat_assigned,
-        :requires_licensed_seat_sm, :duo_chat_enabled_for_user) do
-        true  | false | ref(:tomorrow)  | false | false | be_disallowed(policy)
-        true  | true  | ref(:tomorrow)  | false | false | be_allowed(policy)
-        true  | true  | ref(:tomorrow)  | false | true  | be_disallowed(policy)
-        false | false | ref(:tomorrow)  | false | false | be_disallowed(policy)
-        false | true  | ref(:tomorrow)  | false | false | be_disallowed(policy)
-        false | true  | ref(:tomorrow)  | true  | false | be_disallowed(policy)
-        false | true  | ref(:yesterday) | false | false | be_disallowed(policy)
-        false | true  | ref(:yesterday) | true  | false | be_disallowed(policy)
-        false | false | ref(:yesterday) | true  | false | be_disallowed(policy)
-        true  | false | ref(:yesterday) | true  | false | be_allowed(policy)
-        true  | false | ref(:yesterday) | true  | true  | be_allowed(policy)
-        true  | true  | ref(:yesterday) | false | false | be_disallowed(policy)
+        :requires_licensed_seat_sm, :self_hosted_duo_chat_enabled, :self_hosted_duo_chat_available,
+        :duo_chat_enabled_for_user) do
+        true  | false | ref(:tomorrow)  | false | false | false | false | be_disallowed(policy)
+        true  | true  | ref(:tomorrow)  | false | false | false | false | be_allowed(policy)
+        true  | true  | ref(:tomorrow)  | false | true  | false | false | be_disallowed(policy)
+        false | false | ref(:tomorrow)  | false | false | false | false | be_disallowed(policy)
+        false | true  | ref(:tomorrow)  | false | false | false | false | be_disallowed(policy)
+        false | true  | ref(:tomorrow)  | true  | false | false | false | be_disallowed(policy)
+        false | true  | ref(:yesterday) | false | false | false | false | be_disallowed(policy)
+        false | true  | ref(:yesterday) | true  | false | false | false | be_disallowed(policy)
+        false | false | ref(:yesterday) | true  | false | false | false | be_disallowed(policy)
+        true  | false | ref(:yesterday) | true  | false | false | false | be_allowed(policy)
+        true  | false | ref(:yesterday) | true  | true  | false | false | be_allowed(policy)
+        true  | true  | ref(:yesterday) | false | false | false | false | be_disallowed(policy)
+        true  | true  | ref(:yesterday) | false | false | true  | false | be_disallowed(policy)
+        true  | true  | ref(:yesterday) | false | false | true  | true  | be_allowed(policy)
       end
 
       with_them do
@@ -699,6 +716,16 @@
           allow(CloudConnector::AvailableServices).to receive(:find_by_name)
                                                         .with(:duo_chat).and_return(duo_chat_service_data)
           allow(duo_chat_service_data).to receive(:allowed_for?).with(current_user).and_return(duo_pro_seat_assigned)
+
+          allow(::Ai::FeatureSetting).to receive_message_chain(:find_by_feature,
+            :self_hosted?).and_return(self_hosted_duo_chat_enabled)
+          self_hosted_service_data = instance_double(CloudConnector::SelfSigned::AvailableServiceData)
+          allow(CloudConnector::AvailableServices).to receive(:find_by_name).with(:self_hosted_models)
+                                                                            .and_return(self_hosted_service_data)
+          allow(self_hosted_service_data).to receive(:allowed_for?)
+            .with(current_user).and_return(self_hosted_duo_chat_available)
+          allow(self_hosted_service_data).to receive(:free_access?)
+            .and_return(self_hosted_duo_chat_available)
         end
 
         it { is_expected.to duo_chat_enabled_for_user }