diff --git a/spec/file_caching_test.js b/spec/file_caching_test.js
index c545637e4046f8c714c28c310018e41ed51abf94..cf11011fdd17445a7abb548243479cd1f8d6ddca 100644
--- a/spec/file_caching_test.js
+++ b/spec/file_caching_test.js
@@ -41,7 +41,8 @@ describe('File caching service', () => {
       '/test.txt': (req, res) => res.setHeader('content-type', 'text/plain').status(200).send('this is test'),
       '/index.html.js': (req, res) => res.setHeader('content-type', 'application/javascript').status(200).send('this is index.html.js'),
       '/index.html': (req, res) => res.setHeader('content-type', 'text/html').status(200).send('<html><head></head><body>it\'s me</body></html>'),
-      '/main.css': (req, res) => res.setHeader('content-type', 'text/css').status(200).send('.foo { color: #000; }')
+      '/main.css': (req, res) => res.setHeader('content-type', 'text/css').status(200).send('.foo { color: #000; }'),
+      '/favicon.ico': 'not really a favicon, though'
 
     })
     await request(app).get('/ready')
@@ -77,4 +78,15 @@ describe('File caching service', () => {
     expect(response.headers['content-type']).toBe('text/html; charset=utf-8')
     expect(response.text).toBe('<html><head></head><body>it\'s me</body></html>')
   })
+
+  it('directly fetches files not referenced in manifest.json files from the upstream servers', async () => {
+    const response = await request(app).get('/favicon.ico')
+    expect(response.statusCode).toBe(200)
+    expect(response.body).toBe('not really a favicon, though')
+  })
+
+  it('returns 404 if file can not be resolved', async () => {
+    const response = await request(app).get('/unknown-file.txt')
+    expect(response.statusCode).toBe(404)
+  })
 })
diff --git a/src/config.js b/src/config.js
new file mode 100644
index 0000000000000000000000000000000000000000..2747ac60a64b32f682f56bcb859c02cd8f0ef1d7
--- /dev/null
+++ b/src/config.js
@@ -0,0 +1,17 @@
+import fs from 'fs/promises'
+import yaml from 'js-yaml'
+
+class Config {
+  async load () {
+    const urlsSource = await fs.readFile('./config/manifests/urls.yaml', 'utf8')
+    this._urls = yaml.load(urlsSource).manifests
+  }
+
+  get urls () {
+    return this._urls || []
+  }
+}
+
+export const config = new Config()
+
+export default config
diff --git a/src/createApp.js b/src/createApp.js
index f68d9b312fa3a1b2595b156bbf6d8c0819d84b0b..ef282800548e0ee0097bee2f40251e511a2d5aaf 100644
--- a/src/createApp.js
+++ b/src/createApp.js
@@ -90,6 +90,14 @@ export function createApp () {
     next()
   })
 
+  app.use(async (req, res, next) => {
+    try {
+      const { 'content-type': contentType, content } = await fileCache.fetchAndStore(req.path)
+      if (content) return res.setHeader('content-type', contentType).status(200).send(content)
+    } catch (e) {}
+    next()
+  })
+
   app.use(function (err, req, res, next) {
     logger.error(err)
     res.status(500).end()
diff --git a/src/files.js b/src/files.js
index b059dc3c83f0dfbb9a739012d8f4e11aaea608e6..6bc4d9ca1fc9b132ad87cf145796254ab62a557e 100644
--- a/src/files.js
+++ b/src/files.js
@@ -1,6 +1,18 @@
 import fetch from 'node-fetch'
 import crypto from 'crypto'
+import { config } from './config.js'
 
+async function fetchData (path, baseUrl) {
+  const response = await fetch(new URL(path, baseUrl))
+  if (!response.ok) return null
+  const content = await response.buffer()
+  const sha256Sum = crypto.createHash('sha256').update(content).digest('base64')
+  return [path, {
+    'content-type': response.headers.get('content-type'),
+    sha256Sum,
+    content
+  }]
+}
 class FileCache {
   constructor () {
     this._cache = {}
@@ -23,15 +35,7 @@ class FileCache {
               console.error('could not find manifest for', file)
               return null
             }
-            const response = await fetch(new URL(file, manifest.meta.baseUrl))
-            if (!response.ok) return null
-            const content = await response.buffer()
-            const sha256Sum = crypto.createHash('sha256').update(content).digest('base64')
-            return [file, {
-              'content-type': response.headers.get('content-type'),
-              sha256Sum,
-              content
-            }]
+            return fetchData(file, manifest.meta.baseUrl)
           } catch (e) { console.error(e) }
         }))).filter(data => Array.isArray(data) && data.length === 2))
       }
@@ -40,6 +44,13 @@ class FileCache {
     this._cache = cache
   }
 
+  async fetchAndStore (path) {
+    if (config.urls.length === 0) await config.load()
+    const [key, value] = await Promise.race(config.urls.map(baseUrl => fetchData(path, baseUrl)))
+    this._cache[key] = value
+    return value
+  }
+
   get (path) {
     return this?._cache[path.slice(1)] || {}
   }
diff --git a/src/manifests.js b/src/manifests.js
index 44b925028d725c5be9acf5f6c250d8c197983f16..a043e2786f67ce4a784793c892433f574d0a6546 100644
--- a/src/manifests.js
+++ b/src/manifests.js
@@ -1,9 +1,8 @@
-import fs from 'fs/promises'
-import yaml from 'js-yaml'
 import fetch from 'node-fetch'
 import path from 'path'
 import { URL } from 'url'
 import { fileCache } from './files.js'
+import { config } from './config.js'
 
 export const loadViteManifests = (() => {
   let cache
@@ -13,12 +12,10 @@ export const loadViteManifests = (() => {
     const CACHE_TTL = parseInt(process.env.CACHE_TTL)
 
     if (!cache || useCache === false || +new Date() > lastCacheTime + CACHE_TTL) {
-      const urlsSource = await fs.readFile('./config/manifests/urls.yaml', 'utf8')
-      const urls = yaml.load(urlsSource).manifests
-
+      await config.load()
       // vite manifests contains a set of objects with the vite-manifests
       // from the corresponding registered services
-      const viteManifests = await Promise.all(urls.map(async url => {
+      const viteManifests = await Promise.all(config.urls.map(async url => {
         const { origin } = new URL(url)
         // fetch the manifests
         const result = await fetch(url)