diff --git a/debian/patches/cp-improve-symlink-handling.patch b/debian/patches/cp-improve-symlink-handling.patch
new file mode 100644
index 0000000000000000000000000000000000000000..2ce8c96eceb946255b4e0db9708992650c4123a4
--- /dev/null
+++ b/debian/patches/cp-improve-symlink-handling.patch
@@ -0,0 +1,194 @@
+From: Michael Debertol <michael.debertol@gmail.com>
+Date: Thu, 17 Jun 2021 22:26:13 +0200
+Subject: cp: improve symlink handling
+
+Origin: upstream, https://github.com/uutils/coreutils/commit/12a1c87cb8e72f0df8b03594f425f8c98268ec1c
+---
+ src/uu/cp/src/cp.rs      | 79 +++++++++++++++++++++++++++---------------------
+ tests/by-util/test_cp.rs | 26 ++++++++++++++++
+ 2 files changed, 70 insertions(+), 35 deletions(-)
+
+diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs
+index 569ee78..d6420fb 100644
+--- a/src/uu/cp/src/cp.rs
++++ b/src/uu/cp/src/cp.rs
+@@ -47,7 +47,6 @@ use std::os::windows::ffi::OsStrExt;
+ use std::path::{Path, PathBuf, StripPrefixError};
+ use std::str::FromStr;
+ use std::string::ToString;
+-use uucore::fs::resolve_relative_path;
+ use uucore::fs::{canonicalize, CanonicalizeMode};
+ use walkdir::WalkDir;
+
+@@ -202,7 +201,6 @@ pub struct Options {
+     copy_contents: bool,
+     copy_mode: CopyMode,
+     dereference: bool,
+-    no_dereference: bool,
+     no_target_dir: bool,
+     one_file_system: bool,
+     overwrite: OverwriteMode,
+@@ -620,11 +618,12 @@ impl Options {
+             attributes_only: matches.is_present(OPT_ATTRIBUTES_ONLY),
+             copy_contents: matches.is_present(OPT_COPY_CONTENTS),
+             copy_mode: CopyMode::from_matches(matches),
+-            dereference: matches.is_present(OPT_DEREFERENCE),
+             // No dereference is set with -p, -d and --archive
+-            no_dereference: matches.is_present(OPT_NO_DEREFERENCE)
++            dereference: !(matches.is_present(OPT_NO_DEREFERENCE)
+                 || matches.is_present(OPT_NO_DEREFERENCE_PRESERVE_LINKS)
+-                || matches.is_present(OPT_ARCHIVE),
++                || matches.is_present(OPT_ARCHIVE)
++                || recursive)
++                || matches.is_present(OPT_DEREFERENCE),
+             one_file_system: matches.is_present(OPT_ONE_FILE_SYSTEM),
+             overwrite: OverwriteMode::from_matches(matches),
+             parents: matches.is_present(OPT_PARENTS),
+@@ -876,7 +875,14 @@ fn copy_source(
+     options: &Options,
+ ) -> CopyResult<()> {
+     let source_path = Path::new(&source);
+-    if source_path.is_dir() {
++    // if no-dereference is enabled and this is a symlink, don't treat it as a directory
++    if source_path.is_dir()
++        && !(!options.dereference
++            && fs::symlink_metadata(source_path)
++                .unwrap()
++                .file_type()
++                .is_symlink())
++    {
+         // Copy as directory
+         copy_directory(source, target, options)
+     } else {
+@@ -917,7 +923,7 @@ fn copy_directory(root: &Path, target: &Target, options: &Options) -> CopyResult
+         return Err(format!("omitting directory '{}'", root.display()).into());
+     }
+
+-    let root_path = Path::new(&root).canonicalize()?;
++    let root_path = env::current_dir().unwrap().join(root);
+
+     let root_parent = if target.exists() {
+         root_path.parent()
+@@ -938,17 +944,15 @@ fn copy_directory(root: &Path, target: &Target, options: &Options) -> CopyResult
+     #[cfg(any(windows, target_os = "redox"))]
+     let mut hard_links: Vec<(String, u64)> = vec![];
+
+-    for path in WalkDir::new(root).same_file_system(options.one_file_system) {
++    for path in WalkDir::new(root)
++        .same_file_system(options.one_file_system)
++        .follow_links(options.dereference)
++    {
+         let p = or_continue!(path);
+         let is_symlink = fs::symlink_metadata(p.path())?.file_type().is_symlink();
+-        let path = if (options.no_dereference || options.dereference) && is_symlink {
+-            // we are dealing with a symlink. Don't follow it
+-            match env::current_dir() {
+-                Ok(cwd) => cwd.join(resolve_relative_path(p.path())),
+-                Err(e) => crash!(1, "failed to get current directory {}", e),
+-            }
+-        } else {
+-            or_continue!(p.path().canonicalize())
++        let path = match env::current_dir() {
++            Ok(cwd) => cwd.join(&p.path()),
++            Err(e) => crash!(1, "failed to get current directory {}", e),
+         };
+
+         let local_to_root_parent = match root_parent {
+@@ -972,9 +976,10 @@ fn copy_directory(root: &Path, target: &Target, options: &Options) -> CopyResult
+         };
+
+         let local_to_target = target.join(&local_to_root_parent);
+-
+-        if path.is_dir() && !local_to_target.exists() {
+-            or_continue!(fs::create_dir_all(local_to_target.clone()));
++        if is_symlink && !options.dereference {
++            copy_link(&path, &local_to_target)?;
++        } else if path.is_dir() && !local_to_target.exists() {
++            or_continue!(fs::create_dir_all(local_to_target));
+         } else if !path.is_dir() {
+             if preserve_hard_links {
+                 let mut found_hard_link = false;
+@@ -1232,22 +1237,8 @@ fn copy_helper(source: &Path, dest: &Path, options: &Options) -> CopyResult<()>
+                 ReflinkMode::Never => {}
+             }
+         }
+-    } else if options.no_dereference && fs::symlink_metadata(&source)?.file_type().is_symlink() {
+-        // Here, we will copy the symlink itself (actually, just recreate it)
+-        let link = fs::read_link(&source)?;
+-        let dest: Cow<'_, Path> = if dest.is_dir() {
+-            match source.file_name() {
+-                Some(name) => dest.join(name).into(),
+-                None => crash!(
+-                    EXIT_ERR,
+-                    "cannot stat ‘{}’: No such file or directory",
+-                    source.display()
+-                ),
+-            }
+-        } else {
+-            dest.into()
+-        };
+-        symlink_file(&link, &dest, &*context_for(&link, &dest))?;
++    } else if !options.dereference && fs::symlink_metadata(&source)?.file_type().is_symlink() {
++        copy_link(source, dest)?;
+     } else if source.to_string_lossy() == "/dev/null" {
+         /* workaround a limitation of fs::copy
+          * https://github.com/rust-lang/rust/issues/79390
+@@ -1264,6 +1255,24 @@ fn copy_helper(source: &Path, dest: &Path, options: &Options) -> CopyResult<()>
+     Ok(())
+ }
+
++fn copy_link(source: &Path, dest: &Path) -> CopyResult<()> {
++    // Here, we will copy the symlink itself (actually, just recreate it)
++    let link = fs::read_link(&source)?;
++    let dest: Cow<'_, Path> = if dest.is_dir() {
++        match source.file_name() {
++            Some(name) => dest.join(name).into(),
++            None => crash!(
++                EXIT_ERR,
++                "cannot stat ‘{}’: No such file or directory",
++                source.display()
++            ),
++        }
++    } else {
++        dest.into()
++    };
++    symlink_file(&link, &dest, &*context_for(&link, &dest))
++}
++
+ /// Generate an error message if `target` is not the correct `target_type`
+ pub fn verify_target_type(target: &Path, target_type: &TargetType) -> CopyResult<()> {
+     match (target_type, target.is_dir()) {
+diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs
+index 1fa8212..bac7e2a 100644
+--- a/tests/by-util/test_cp.rs
++++ b/tests/by-util/test_cp.rs
+@@ -1074,3 +1074,29 @@ fn test_cp_one_file_system() {
+         }
+     }
+ }
++
++#[test]
++fn test_copy_dir_symlink() {
++    let (at, mut ucmd) = at_and_ucmd!();
++    at.mkdir("dir");
++    at.symlink_dir("dir", "dir-link");
++    ucmd.args(&["-r", "dir-link", "copy"]).succeeds();
++    assert_eq!(at.resolve_link("copy"), "dir");
++}
++
++#[test]
++fn test_copy_dir_with_symlinks() {
++    let (at, mut ucmd) = at_and_ucmd!();
++    at.mkdir("dir");
++    at.make_file("dir/file");
++
++    TestScenario::new("ln")
++        .ucmd()
++        .arg("-sr")
++        .arg(at.subdir.join("dir/file"))
++        .arg(at.subdir.join("dir/file-link"))
++        .succeeds();
++
++    ucmd.args(&["-r", "dir", "copy"]).succeeds();
++    assert_eq!(at.resolve_link("copy/file-link"), "file");
++}
diff --git a/debian/patches/cp-move-symlink-check-to-the-right-place.patch b/debian/patches/cp-move-symlink-check-to-the-right-place.patch
new file mode 100644
index 0000000000000000000000000000000000000000..95e411b9e22d8256d49798924838bca011b1270c
--- /dev/null
+++ b/debian/patches/cp-move-symlink-check-to-the-right-place.patch
@@ -0,0 +1,41 @@
+From: Michael Debertol <michael.debertol@gmail.com>
+Date: Fri, 18 Jun 2021 11:42:37 +0200
+Subject: cp: move symlink check to the right place
+
+Origin: upstream, https://github.com/uutils/coreutils/commit/315bfd65a3f07221abdb262ba8731d364441e581
+---
+ src/uu/cp/src/cp.rs | 14 ++++++--------
+ 1 file changed, 6 insertions(+), 8 deletions(-)
+
+diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs
+index d6420fb..4b93b3a 100644
+--- a/src/uu/cp/src/cp.rs
++++ b/src/uu/cp/src/cp.rs
+@@ -875,14 +875,7 @@ fn copy_source(
+     options: &Options,
+ ) -> CopyResult<()> {
+     let source_path = Path::new(&source);
+-    // if no-dereference is enabled and this is a symlink, don't treat it as a directory
+-    if source_path.is_dir()
+-        && !(!options.dereference
+-            && fs::symlink_metadata(source_path)
+-                .unwrap()
+-                .file_type()
+-                .is_symlink())
+-    {
++    if source_path.is_dir() {
+         // Copy as directory
+         copy_directory(source, target, options)
+     } else {
+@@ -923,6 +916,11 @@ fn copy_directory(root: &Path, target: &Target, options: &Options) -> CopyResult
+         return Err(format!("omitting directory '{}'", root.display()).into());
+     }
+
++    // if no-dereference is enabled and this is a symlink, copy it as a file
++    if !options.dereference && fs::symlink_metadata(root).unwrap().file_type().is_symlink() {
++        return copy_file(root, target, options);
++    }
++
+     let root_path = env::current_dir().unwrap().join(root);
+
+     let root_parent = if target.exists() {
diff --git a/debian/patches/series b/debian/patches/series
index cfcf418ddfbdc1646dcc1840f2595d448cbc706d..ca4378e7c6c1c6a1f4b256c2bb8f75286c70a717 100644
--- a/debian/patches/series
+++ b/debian/patches/series
@@ -14,6 +14,8 @@ Sort-Implement-stable-sort-ignore-non-printing-month.patch
 Ignore-a-test.patch
 Sort-Various-fixes-and-performance-improvements.patch
 sort-implement-k-and-t-support.patch
+cp-improve-symlink-handling.patch
+cp-move-symlink-check-to-the-right-place.patch
 
 # Apertis patches
 sort-fix-dependencies-versions.patch