diff --git a/bigbluebutton-html5/imports/api/audio/client/bridge/kurento.js b/bigbluebutton-html5/imports/api/audio/client/bridge/kurento.js
index 7f1e4aeffda6bf3ab08218edeb2837bdeb394aad..3d4625731c6e82eb82729a24e1921e4e6cd35d57 100755
--- a/bigbluebutton-html5/imports/api/audio/client/bridge/kurento.js
+++ b/bigbluebutton-html5/imports/api/audio/client/bridge/kurento.js
@@ -1,6 +1,6 @@
 import BaseAudioBridge from './base';
 import Auth from '/imports/ui/services/auth';
-import { fetchWebRTCMappedStunTurnServers } from '/imports/utils/fetchStunTurnServers';
+import { fetchWebRTCMappedStunTurnServers, getMappedFallbackStun } from '/imports/utils/fetchStunTurnServers';
 import playAndRetry from '/imports/utils/mediaElementPlayRetry';
 import logger from '/imports/startup/client/logger';
 
@@ -64,6 +64,7 @@ export default class KurentoAudioBridge extends BaseAudioBridge {
       } catch (error) {
         logger.error({ logCode: 'sfuaudiobridge_stunturn_fetch_failed' },
           'SFU audio bridge failed to fetch STUN/TURN info, using default servers');
+        iceServers = getMappedFallbackStun();
       } finally {
         logger.debug({ logCode: 'sfuaudiobridge_stunturn_fetch_sucess', extraInfo: { iceServers } },
           'SFU audio bridge got STUN/TURN servers');
diff --git a/bigbluebutton-html5/imports/api/audio/client/bridge/sip.js b/bigbluebutton-html5/imports/api/audio/client/bridge/sip.js
index 6411026f5477d95647b3cb8c8b00dd1f767679c1..55688ef261e5c3c86fbc1e23b0c46a08b99513dd 100755
--- a/bigbluebutton-html5/imports/api/audio/client/bridge/sip.js
+++ b/bigbluebutton-html5/imports/api/audio/client/bridge/sip.js
@@ -1,7 +1,7 @@
 import browser from 'browser-detect';
 import BaseAudioBridge from './base';
 import logger from '/imports/startup/client/logger';
-import { fetchStunTurnServers } from '/imports/utils/fetchStunTurnServers';
+import { fetchStunTurnServers, getFallbackStun } from '/imports/utils/fetchStunTurnServers';
 import {
   isUnifiedPlan,
   toUnifiedPlan,
@@ -85,6 +85,22 @@ class SIPSession {
     });
   }
 
+  async getIceServers (sessionToken) {
+    try {
+      const iceServers = await fetchStunTurnServers(sessionToken);
+      return iceServers;
+    } catch (error) {
+      logger.error({
+        logCode: 'sip_js_fetchstunturninfo_error',
+        extraInfo: {
+          errorCode: error.code,
+          errorMessage: error.message,
+        },
+      }, 'Full audio bridge failed to fetch STUN/TURN info');
+      return getFallbackStun();
+    }
+  }
+
   doCall(options) {
     const {
       isListenOnly,
@@ -105,7 +121,7 @@ class SIPSession {
     this.user.callerIdName = callerIdName;
     this.callOptions = options;
 
-    return fetchStunTurnServers(sessionToken)
+    return this.getIceServers(sessionToken)
       .then(this.createUserAgent.bind(this))
       .then(this.inviteUserAgent.bind(this))
       .then(this.setupEventHandlers.bind(this));
diff --git a/bigbluebutton-html5/imports/api/screenshare/client/bridge/kurento.js b/bigbluebutton-html5/imports/api/screenshare/client/bridge/kurento.js
index 3ad3aa1c251133caebe974ee5a7c9b34479a732d..47a1401d1cce0728daf290b0bfd51a13c616a741 100755
--- a/bigbluebutton-html5/imports/api/screenshare/client/bridge/kurento.js
+++ b/bigbluebutton-html5/imports/api/screenshare/client/bridge/kurento.js
@@ -1,6 +1,6 @@
 import Auth from '/imports/ui/services/auth';
 import BridgeService from './service';
-import { fetchWebRTCMappedStunTurnServers } from '/imports/utils/fetchStunTurnServers';
+import { fetchWebRTCMappedStunTurnServers, getMappedFallbackStun } from '/imports/utils/fetchStunTurnServers';
 import playAndRetry from '/imports/utils/mediaElementPlayRetry';
 import logger from '/imports/startup/client/logger';
 
@@ -72,6 +72,7 @@ export default class KurentoScreenshareBridge {
     } catch (error) {
       logger.error({ logCode: 'screenshare_viwer_fetchstunturninfo_error', extraInfo: { error } },
         'Screenshare bridge failed to fetch STUN/TURN info, using default');
+      iceServers = getMappedFallbackStun();
     } finally {
       const options = {
         wsUrl: Auth.authenticateURL(SFU_URL),
@@ -168,6 +169,7 @@ export default class KurentoScreenshareBridge {
     } catch (error) {
       logger.error({ logCode: 'screenshare_presenter_fetchstunturninfo_error' },
         'Screenshare bridge failed to fetch STUN/TURN info, using default');
+      iceServers = getMappedFallbackStun();
     } finally {
       const options = {
         wsUrl: Auth.authenticateURL(SFU_URL),
diff --git a/bigbluebutton-html5/imports/ui/components/video-provider/component.jsx b/bigbluebutton-html5/imports/ui/components/video-provider/component.jsx
index d2ef0306a02da5e2b7fd39ae08466c443425ec95..79070fa000d2745a506106873be269b3927e2f2b 100755
--- a/bigbluebutton-html5/imports/ui/components/video-provider/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/video-provider/component.jsx
@@ -3,7 +3,10 @@ import { defineMessages, injectIntl } from 'react-intl';
 import { Session } from 'meteor/session';
 import { notify } from '/imports/ui/services/notification';
 import VisibilityEvent from '/imports/utils/visibilityEvent';
-import { fetchWebRTCMappedStunTurnServers } from '/imports/utils/fetchStunTurnServers';
+import {
+  fetchWebRTCMappedStunTurnServers,
+  getMappedFallbackStun,
+} from '/imports/utils/fetchStunTurnServers';
 import PropTypes from 'prop-types';
 import ReconnectingWebSocket from 'reconnecting-websocket';
 import logger from '/imports/startup/client/logger';
@@ -608,9 +611,12 @@ class VideoProvider extends Component {
       logger.error({
         logCode: 'video_provider_fetchstunturninfo_error',
         extraInfo: {
-          error,
+          errorCode: error.code,
+          errorMessage: error.message,
         },
       }, 'video-provider failed to fetch STUN/TURN info, using default');
+      // Use fallback STUN server
+      iceServers = getMappedFallbackStun();
     } finally {
       const { constraints, bitrate, id: profileId } = VideoProvider.getCameraProfile();
       this.outboundIceQueues[id] = [];
diff --git a/bigbluebutton-html5/imports/utils/fetchStunTurnServers.js b/bigbluebutton-html5/imports/utils/fetchStunTurnServers.js
index ee90b03d21796a26e44d15e2a8f3f7a399802563..4400364e503f6e46ec1148db4a870904905541ff 100644
--- a/bigbluebutton-html5/imports/utils/fetchStunTurnServers.js
+++ b/bigbluebutton-html5/imports/utils/fetchStunTurnServers.js
@@ -2,11 +2,18 @@ import _ from 'lodash';
 
 const MEDIA = Meteor.settings.public.media;
 const STUN_TURN_FETCH_URL = MEDIA.stunTurnServersFetchAddress;
+const CACHE_STUN_TURN = MEDIA.cacheStunTurnServers;
+const FALLBACK_STUN_SERVER = MEDIA.fallbackStunServer;
+
+let STUN_TURN_DICT;
+let MAPPED_STUN_TURN_DICT;
 
 const fetchStunTurnServers = function (sessionToken) {
+  if (STUN_TURN_DICT && CACHE_STUN_TURN) return Promise.resolve(STUN_TURN_DICT);
+
   const handleStunTurnResponse = ({ stunServers, turnServers }) => {
     if (!stunServers && !turnServers) {
-      return { error: 404, stun: [], turn: [] };
+      return Promise.reject(new Error('Could not fetch STUN/TURN servers'));
     }
 
     const turnReply = [];
@@ -19,35 +26,57 @@ const fetchStunTurnServers = function (sessionToken) {
       });
     });
 
-    return {
+    const stDictionary = {
       stun: stunServers.map(server => server.url),
       turn: turnReply,
     };
+
+    STUN_TURN_DICT = stDictionary;
+
+    return Promise.resolve(stDictionary);
   };
 
   const url = `${STUN_TURN_FETCH_URL}?sessionToken=${sessionToken}`;
   return fetch(url, { credentials: 'same-origin' })
     .then(res => res.json())
     .then(handleStunTurnResponse)
-    .then((response) => {
-      if (response.error) {
-        return Promise.reject('Could not fetch the stuns/turns servers!');
-      }
-      return response;
-    });
 };
 
+const mapStunTurn = ({ stun, turn }) => {
+  const rtcStuns = stun.map(url => ({ urls: url }));
+  const rtcTurns = turn.map(t => ({ urls: t.urls, credential: t.password, username: t.username }));
+  return rtcStuns.concat(rtcTurns);
+};
+
+const getFallbackStun = () => {
+  const stun = FALLBACK_STUN_SERVER ? [FALLBACK_STUN_SERVER] : []
+  return { stun, turn: [] };
+}
+
+const getMappedFallbackStun = () => {
+  return FALLBACK_STUN_SERVER ? [{ urls: FALLBACK_STUN_SERVER }] : [];
+}
+
 const fetchWebRTCMappedStunTurnServers = function (sessionToken) {
   return new Promise(async (resolve, reject) => {
     try {
-      const { stun, turn } = await fetchStunTurnServers(sessionToken);
-      const rtcStuns = stun.map(url => ({ urls: url }));
-      const rtcTurns = turn.map(t => ({ urls: t.urls, credential: t.password, username: t.username }));
-      return resolve(rtcStuns.concat(rtcTurns));
+      if (MAPPED_STUN_TURN_DICT && CACHE_STUN_TURN) {
+        return resolve(MAPPED_STUN_TURN_DICT);
+      }
+
+      const stDictionary =  await fetchStunTurnServers(sessionToken);
+
+      MAPPED_STUN_TURN_DICT = mapStunTurn(stDictionary);
+      return resolve(MAPPED_STUN_TURN_DICT);
     } catch (error) {
       return reject(error);
     }
   });
 };
 
-export { fetchStunTurnServers, fetchWebRTCMappedStunTurnServers };
+export {
+  fetchStunTurnServers,
+  fetchWebRTCMappedStunTurnServers,
+  getFallbackStun,
+  getMappedFallbackStun,
+};
diff --git a/bigbluebutton-html5/imports/utils/safari-webrtc.js b/bigbluebutton-html5/imports/utils/safari-webrtc.js
index f1101cc146a0834cf999d86832d108acdd86df10..01d1149f7c1c1359f14f7e333394ca52cbb675c2 100644
--- a/bigbluebutton-html5/imports/utils/safari-webrtc.js
+++ b/bigbluebutton-html5/imports/utils/safari-webrtc.js
@@ -1,34 +1,19 @@
-import { fetchWebRTCMappedStunTurnServers } from '/imports/utils/fetchStunTurnServers';
+import {
+  fetchWebRTCMappedStunTurnServers,
+  getMappedFallbackStun,
+} from '/imports/utils/fetchStunTurnServers';
 import Auth from '/imports/ui/services/auth';
 import { Session } from 'meteor/session';
 import logger from '/imports/startup/client/logger';
 
-const defaultIceServersList = [
-  { urls: 'stun:stun.l.google.com:19302' },
-  { urls: 'stun:stun1.l.google.com:19302' },
-  { urls: 'stun:stun2.l.google.com:19302' },
-  { urls: 'stun:stun3.l.google.com:19302' },
-  { urls: 'stun:stun4.l.google.com:19302' },
-  { urls: 'stun:stun.ekiga.net' },
-  { urls: 'stun:stun.ideasip.com' },
-  { urls: 'stun:stun.schlund.de' },
-  { urls: 'stun:stun.stunprotocol.org:3478' },
-  { urls: 'stun:stun.voiparound.com' },
-  { urls: 'stun:stun.voipbuster.com' },
-  { urls: 'stun:stun.voipstunt.com' },
-  { urls: 'stun:stun.voxgratia.org' },
-  { urls: 'stun:stun.services.mozilla.com' },
-];
-
 const getSessionToken = () => Auth.sessionToken;
 
 export async function getIceServersList() {
   try {
     const iceServers = await fetchWebRTCMappedStunTurnServers(getSessionToken());
-
-    return iceServers || defaultIceServersList;
+    return iceServers;
   } catch (error) {
-    return defaultIceServersList;
+    return getMappedFallbackStun();
   }
 }
 
diff --git a/bigbluebutton-html5/private/config/settings.yml b/bigbluebutton-html5/private/config/settings.yml
index b40d1296e3782ce5725498319ac43cce96a61e7d..3959c8777cc7958b5e38f3031d78a37192d1bf8d 100755
--- a/bigbluebutton-html5/private/config/settings.yml
+++ b/bigbluebutton-html5/private/config/settings.yml
@@ -170,6 +170,8 @@ public:
     hidePresentation: false
   media:
     stunTurnServersFetchAddress: "/bigbluebutton/api/stuns"
+    cacheStunTurnServers: true
+    fallbackStunServer:  ''
     mediaTag: "#remote-media"
     callTransferTimeout: 5000
     callHangupTimeout: 2000