diff --git a/app/controllers/concerns/snippets_actions.rb b/app/controllers/concerns/snippets_actions.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ca6dffe1cc57ac1f755547d9adc163f0826579f0
--- /dev/null
+++ b/app/controllers/concerns/snippets_actions.rb
@@ -0,0 +1,21 @@
+module SnippetsActions
+  extend ActiveSupport::Concern
+
+  def edit
+  end
+
+  def raw
+    send_data(
+      convert_line_endings(@snippet.content),
+      type: 'text/plain; charset=utf-8',
+      disposition: 'inline',
+      filename: @snippet.sanitized_file_name
+    )
+  end
+
+  private
+
+  def convert_line_endings(content)
+    params[:line_ending] == 'raw' ? content : content.gsub(/\r\n/, "\n")
+  end
+end
diff --git a/app/controllers/projects/snippets_controller.rb b/app/controllers/projects/snippets_controller.rb
index 5d193f26a8e222efa51aa0e175ce114d331d3ca3..ef5d3d242eb3e6c50453048d883317c17b35f72e 100644
--- a/app/controllers/projects/snippets_controller.rb
+++ b/app/controllers/projects/snippets_controller.rb
@@ -1,6 +1,7 @@
 class Projects::SnippetsController < Projects::ApplicationController
   include ToggleAwardEmoji
   include SpammableActions
+  include SnippetsActions
 
   before_action :module_enabled
   before_action :snippet, only: [:show, :edit, :destroy, :update, :raw, :toggle_award_emoji, :mark_as_spam]
@@ -49,9 +50,6 @@ class Projects::SnippetsController < Projects::ApplicationController
     end
   end
 
-  def edit
-  end
-
   def update
     UpdateSnippetService.new(project, current_user, @snippet,
                              snippet_params).execute
@@ -74,15 +72,6 @@ class Projects::SnippetsController < Projects::ApplicationController
     redirect_to namespace_project_snippets_path(@project.namespace, @project)
   end
 
-  def raw
-    send_data(
-      @snippet.content,
-      type: 'text/plain; charset=utf-8',
-      disposition: 'inline',
-      filename: @snippet.sanitized_file_name
-    )
-  end
-
   protected
 
   def snippet
diff --git a/app/controllers/snippets_controller.rb b/app/controllers/snippets_controller.rb
index b169d9936885130e93a3b8ef62b9bef735292903..366804ab17e6a3e8062bbf7429573adad5634c8b 100644
--- a/app/controllers/snippets_controller.rb
+++ b/app/controllers/snippets_controller.rb
@@ -1,6 +1,7 @@
 class SnippetsController < ApplicationController
   include ToggleAwardEmoji
   include SpammableActions
+  include SnippetsActions
 
   before_action :snippet, only: [:show, :edit, :destroy, :update, :raw, :download]
 
@@ -47,9 +48,6 @@ class SnippetsController < ApplicationController
     respond_with @snippet.becomes(Snippet)
   end
 
-  def edit
-  end
-
   def update
     UpdateSnippetService.new(nil, current_user, @snippet,
                              snippet_params).execute
@@ -67,18 +65,9 @@ class SnippetsController < ApplicationController
     redirect_to snippets_path
   end
 
-  def raw
-    send_data(
-      @snippet.content,
-      type: 'text/plain; charset=utf-8',
-      disposition: 'inline',
-      filename: @snippet.sanitized_file_name
-    )
-  end
-
   def download
     send_data(
-      @snippet.content,
+      convert_line_endings(@snippet.content),
       type: 'text/plain; charset=utf-8',
       filename: @snippet.sanitized_file_name
     )
diff --git a/changelogs/unreleased/21240_snippets_line_ending.yml b/changelogs/unreleased/21240_snippets_line_ending.yml
new file mode 100644
index 0000000000000000000000000000000000000000..880fdd2c9ede1875e26a393a7f9dda8713245fb1
--- /dev/null
+++ b/changelogs/unreleased/21240_snippets_line_ending.yml
@@ -0,0 +1,4 @@
+---
+title: Download snippets with LF line-endings by default
+merge_request: 8999
+author:
diff --git a/doc/README.md b/doc/README.md
index 1943d656aa78624cfcf4da2b622f913921fa587e..2712206373d4c1b1a303f0340055c76f04f633d3 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -21,6 +21,7 @@
 - [Profile Settings](profile/README.md)
 - [Project Services](user/project/integrations//project_services.md) Integrate a project with external services, such as CI and chat.
 - [Public access](public_access/public_access.md) Learn how you can allow public and internal access to projects.
+- [Snippets](user/snippets.md) Snippets allow you to create little bits of code.
 - [SSH](ssh/README.md) Setup your ssh keys and deploy keys for secure access to your projects.
 - [Webhooks](user/project/integrations/webhooks.md) Let GitLab notify you when new code has been pushed to your project.
 - [Workflow](workflow/README.md) Using GitLab functionality and importing projects from GitHub and SVN.
diff --git a/doc/user/snippets.md b/doc/user/snippets.md
new file mode 100644
index 0000000000000000000000000000000000000000..417360e08ac2d23a8bfa19053920c991b32571c4
--- /dev/null
+++ b/doc/user/snippets.md
@@ -0,0 +1,19 @@
+# Snippets
+
+Snippets are little bits of code or text.
+
+There are 2 types of snippets - project snippets and personal snippets.
+
+## Project snippets
+
+Project snippets are always related to a specific project - see [Project features](../workflow/project_features.md) for more information.
+
+## Personal snippets
+
+Personal snippets are not related to any project and can be created completely independently. There are 3 visibility levels that can be set (public, internal, private - see [Public Access](../public_access/public_access.md) for more information).
+
+## Downloading snippets
+
+You can download the raw content of a snippet.
+
+By default snippets will be downloaded with Linux-style line endings (`LF`). If you want to preserve the original line endings you need to add a parameter `line_ending=raw` (eg. `https://gitlab.com/snippets/SNIPPET_ID/raw?line_ending=raw`). In case a snippet was created using the GitLab web interface the original line ending is Windows-like (`CRLF`).
diff --git a/doc/workflow/README.md b/doc/workflow/README.md
index 7a97b87f1c5493ddcb67bbaad5ebc3d6a0290b4a..9e7ee47387ce51b918b943ba59fcd034bdce31fb 100644
--- a/doc/workflow/README.md
+++ b/doc/workflow/README.md
@@ -39,3 +39,4 @@
 - [Manage large binaries with Git LFS](lfs/manage_large_binaries_with_git_lfs.md)
 - [Importing from SVN, GitHub, Bitbucket, etc](importing/README.md)
 - [Todos](todos.md)
+- [Snippets](../user/snippets.md)
diff --git a/spec/controllers/projects/snippets_controller_spec.rb b/spec/controllers/projects/snippets_controller_spec.rb
index 19e948d8fb8294a38e16b6c1c8f96094bbe32e20..77ee10a1e151ee953f0ebeecf42a885af07c18c9 100644
--- a/spec/controllers/projects/snippets_controller_spec.rb
+++ b/spec/controllers/projects/snippets_controller_spec.rb
@@ -206,4 +206,37 @@ describe Projects::SnippetsController do
       end
     end
   end
+
+  describe 'GET #raw' do
+    let(:project_snippet) do
+      create(
+        :project_snippet, :public,
+        project: project,
+        author: user,
+        content: "first line\r\nsecond line\r\nthird line"
+      )
+    end
+
+    context 'CRLF line ending' do
+      let(:params) do
+        {
+          namespace_id: project.namespace.path,
+          project_id: project.path,
+          id: project_snippet.to_param
+        }
+      end
+
+      it 'returns LF line endings by default' do
+        get :raw, params
+
+        expect(response.body).to eq("first line\nsecond line\nthird line")
+      end
+
+      it 'does not convert line endings when parameter present' do
+        get :raw, params.merge(line_ending: :raw)
+
+        expect(response.body).to eq("first line\r\nsecond line\r\nthird line")
+      end
+    end
+  end
 end
diff --git a/spec/controllers/snippets_controller_spec.rb b/spec/controllers/snippets_controller_spec.rb
index dadcb90cfc2350b362319a443a1fb97a2998d7f5..f90c0d76ceba6626f5186848c3a8d0368da95826 100644
--- a/spec/controllers/snippets_controller_spec.rb
+++ b/spec/controllers/snippets_controller_spec.rb
@@ -286,6 +286,24 @@ describe SnippetsController do
             expect(assigns(:snippet)).to eq(personal_snippet)
             expect(response).to have_http_status(200)
           end
+
+          context 'CRLF line ending' do
+            let(:personal_snippet) do
+              create(:personal_snippet, :public, author: user, content: "first line\r\nsecond line\r\nthird line")
+            end
+
+            it 'returns LF line endings by default' do
+              get action, id: personal_snippet.to_param
+
+              expect(response.body).to eq("first line\nsecond line\nthird line")
+            end
+
+            it 'does not convert line endings when parameter present' do
+              get action, id: personal_snippet.to_param, line_ending: :raw
+
+              expect(response.body).to eq("first line\r\nsecond line\r\nthird line")
+            end
+          end
         end
 
         context 'when not signed in' do