diff --git a/lib/search.rb b/lib/search.rb
index 3daf57c5e76..e9e226d4482 100644
--- a/lib/search.rb
+++ b/lib/search.rb
@@ -188,7 +188,7 @@ class Search
 
     if term.present?
       @term = Search.prepare_data(term, Topic === @search_context ? :topic : nil)
-      @original_term = PG::Connection.escape_string(@term)
+      @original_term = Search.escape_string(@term)
     end
 
     if @search_pms || @opts[:type_filter] == 'private_messages'
@@ -1100,14 +1100,17 @@ class Search
 
   def self.to_tsquery(ts_config: nil, term:, joiner: nil)
     ts_config = ActiveRecord::Base.connection.quote(ts_config) if ts_config
-    tsquery = "TO_TSQUERY(#{ts_config || default_ts_config}, '#{term}')"
-    tsquery = "REPLACE(#{tsquery}::text, '&', '#{PG::Connection.escape_string(joiner)}')::tsquery" if joiner
+    tsquery = "TO_TSQUERY(#{ts_config || default_ts_config}, '#{self.escape_string(term)}')"
+    tsquery = "REPLACE(#{tsquery}::text, '&', '#{self.escape_string(joiner)}')::tsquery" if joiner
     tsquery
   end
 
   def self.set_tsquery_weight_filter(term, weight_filter)
-    term = term.gsub("'", "''")
-    "''#{PG::Connection.escape_string(term)}'':*#{weight_filter}"
+    "'#{self.escape_string(term)}':*#{weight_filter}"
+  end
+
+  def self.escape_string(term)
+    PG::Connection.escape_string(term).gsub('\\', '\\\\\\')
   end
 
   def ts_query(ts_config = nil, weight_filter: nil)
@@ -1237,7 +1240,7 @@ class Search
 
   def posts_scope(default_scope = Post.all)
     if SiteSetting.use_pg_headlines_for_excerpt
-      search_term = @term.present? ? PG::Connection.escape_string(@term) : nil
+      search_term = @term.present? ? Search.escape_string(@term) : nil
       ts_config = default_ts_config
 
       default_scope
diff --git a/spec/components/search_spec.rb b/spec/components/search_spec.rb
index 12e8b92624d..7e9b0cb7976 100644
--- a/spec/components/search_spec.rb
+++ b/spec/components/search_spec.rb
@@ -134,12 +134,20 @@ describe Search do
     expect(search.term).to eq('a b c okaylength')
   end
 
-  it 'escapes non alphanumeric characters' do
-    expect(Search.execute('foo :!$);}]>@\#\"\'').posts.length).to eq(0) # There are at least three levels of sanitation for Search.query!
-  end
+  context 'query sanitizaton' do
+    let!(:post) { Fabricate(:post, raw: 'hello world') }
 
-  it "doesn't raise an error when single quotes are present" do
-    expect(Search.execute("'hello' world").posts.length).to eq(0) # There are at least three levels of sanitation for Search.query!
+    it 'escapes backslash' do
+      expect(Search.execute('hello\\').posts).to contain_exactly(post)
+    end
+
+    it 'escapes single quote' do
+      expect(Search.execute("hello'").posts).to contain_exactly(post)
+    end
+
+    it 'escapes non-alphanumeric characters' do
+      expect(Search.execute('hello :!$);}]>@\#\"\'').posts).to contain_exactly(post)
+    end
   end
 
   it 'works when given two terms with spaces' do
@@ -755,7 +763,7 @@ describe Search do
     let(:result) { Search.execute('запись') }
 
     it 'finds something when given cyrillic query' do
-      expect(result.posts).to be_present
+      expect(result.posts).to contain_exactly(post)
     end
   end
 
@@ -1622,6 +1630,14 @@ describe Search do
       expect { DB.exec(+"SELECT to_tsvector('bbb') @@ " << ts_query) }.to_not raise_error
       expect(ts_query).to include("baz")
     end
+
+    it 'esacpes the term correctly' do
+      expect(Search.ts_query(term: 'Title with trailing backslash\\'))
+        .to eq("TO_TSQUERY('english', '''Title with trailing backslash\\\\\\\\'':*')")
+
+      expect(Search.ts_query(term: "Title with trailing quote'"))
+        .to eq("TO_TSQUERY('english', '''Title with trailing quote'''''':*')")
+    end
   end
 
   context '#word_to_date' do