diff --git a/config/application.rb b/config/application.rb
index eb0daffde..8d1ff37a0 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -135,9 +135,6 @@ module Discourse
     # supports etags (post 1.7)
     config.middleware.delete Rack::ETag
 
-    require 'middleware/apply_cdn'
-    config.middleware.use Middleware::ApplyCDN
-
     # route all exceptions via our router
     config.exceptions_app = self.routes
 
diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml
index 4e6222ed1..feae6e063 100644
--- a/config/locales/server.en.yml
+++ b/config/locales/server.en.yml
@@ -945,6 +945,7 @@ en:
     s3_access_key_id: "The Amazon S3 access key id that will be used to upload images."
     s3_secret_access_key: "The Amazon S3 secret access key that will be used to upload images."
     s3_region: "The Amazon S3 region name that will be used to upload images."
+    s3_cdn_url: "The CDN URL to use for all s3 assets (for example: https://cdn.somewhere.com). WARNING: after changing this setting you must rebake all old posts."
 
     avatar_sizes: "List of automatically generated avatar sizes."
 
diff --git a/config/site_settings.yml b/config/site_settings.yml
index aa38b98d2..088551919 100644
--- a/config/site_settings.yml
+++ b/config/site_settings.yml
@@ -531,7 +531,10 @@ files:
     enum: 'S3RegionSiteSetting'
   s3_upload_bucket:
     default: ''
-    regex: "^[^A-Z._]+$"
+    regex: '^[^A-Z._]+$'
+  s3_cdn_url:
+    default: ''
+    regex: '^https?:\/\/.+[^\/]$'
   allow_profile_backgrounds:
     client: true
     default: true
diff --git a/lib/middleware/apply_cdn.rb b/lib/middleware/apply_cdn.rb
deleted file mode 100644
index 60995b476..000000000
--- a/lib/middleware/apply_cdn.rb
+++ /dev/null
@@ -1,24 +0,0 @@
-module Middleware
-
-  class ApplyCDN
-
-    def initialize(app, settings={})
-      @app = app
-    end
-
-    def call(env)
-      status, headers, response = @app.call(env)
-
-      if Discourse.asset_host.present? &&
-         Discourse.store.external? &&
-        (headers["Content-Type"].start_with?("text/") ||
-         headers["Content-Type"].start_with?("application/json"))
-        response.body = response.body.gsub(Discourse.store.absolute_base_url, Discourse.asset_host)
-      end
-
-      [status, headers, response]
-    end
-
-  end
-
-end
diff --git a/lib/pretty_text.rb b/lib/pretty_text.rb
index de418dc18..9652dc2b5 100644
--- a/lib/pretty_text.rb
+++ b/lib/pretty_text.rb
@@ -208,18 +208,33 @@ module PrettyText
     options[:topicId] = opts[:topic_id]
 
     sanitized = markdown(text.dup, options)
-    sanitized = add_rel_nofollow_to_user_content(sanitized) if !options[:omit_nofollow] && SiteSetting.add_rel_nofollow_to_user_content
-    sanitized
+
+    doc = Nokogiri::HTML.fragment(sanitized)
+
+    if !options[:omit_nofollow] && SiteSetting.add_rel_nofollow_to_user_content
+      add_rel_nofollow_to_user_content(doc)
+    end
+
+    if SiteSetting.s3_cdn_url.present? && SiteSetting.enable_s3_uploads
+      add_s3_cdn(doc)
+    end
+
+    doc.to_html
   end
 
-  def self.add_rel_nofollow_to_user_content(html)
+  def self.add_s3_cdn(doc)
+    doc.css("img").each do |img|
+      img["src"] = img["src"].sub(Discourse.store.absolute_base_url, SiteSetting.s3_cdn_url)
+    end
+  end
+
+  def self.add_rel_nofollow_to_user_content(doc)
     whitelist = []
 
     domains = SiteSetting.exclude_rel_nofollow_domains
     whitelist = domains.split('|') if domains.present?
 
     site_uri = nil
-    doc = Nokogiri::HTML.fragment(html)
     doc.css("a").each do |l|
       href = l["href"].to_s
       begin
@@ -238,7 +253,6 @@ module PrettyText
         l["rel"] = "nofollow"
       end
     end
-    doc.to_html
   end
 
   class DetectedLink
diff --git a/spec/components/pretty_text_spec.rb b/spec/components/pretty_text_spec.rb
index 5b1dfd26c..4f6a0f6f4 100644
--- a/spec/components/pretty_text_spec.rb
+++ b/spec/components/pretty_text_spec.rb
@@ -308,4 +308,17 @@ describe PrettyText do
     expect(PrettyText.cook("```cpp\ncpp\n```")).to match_html("<p></p><pre><code class='lang-cpp'>cpp</code></pre>")
   end
 
+  it 'can substitute s3 cdn correctly' do
+    SiteSetting.enable_s3_uploads = true
+    SiteSetting.s3_access_key_id = "XXX"
+    SiteSetting.s3_secret_access_key = "XXX"
+    SiteSetting.s3_upload_bucket = "test"
+    SiteSetting.s3_cdn_url = "https://awesome.cdn"
+
+    raw = "<img src='#{Discourse.store.absolute_base_url}/original/9/9/99c9384b8b6d87f8509f8395571bc7512ca3cad1.jpg'"
+    cooked = "<p><img src='https://awesome.cdn/original/9/9/99c9384b8b6d87f8509f8395571bc7512ca3cad1.jpg'></p>"
+
+    expect(PrettyText.cook(raw)).to match_html(cooked)
+  end
+
 end