diff --git a/configure.in b/configure.in
index e2d43d1..1320880 100644
--- a/configure.in
+++ b/configure.in
@@ -1496,7 +1496,7 @@ else
   AC_LIBOBJ([signbit])
 fi
 AC_CHECK_FUNCS(fmod killpg wait4 waitpid fork spawnv syscall __syscall chroot getcwd eaccess\
-	      truncate ftruncate ftello chsize times utimes utimensat fcntl lockf lstat\
+	      truncate ftruncate ftello chsize times utimes lutimes utimensat fcntl lockf lstat\
 	      truncate64 ftruncate64 ftello64 fseeko fseeko64 \
 	      link symlink readlink readdir_r fsync fdatasync fchown posix_fadvise\
 	      setitimer setruid seteuid setreuid setresuid socketpair\
diff --git a/file.c b/file.c
index b27e769..012fc12 100644
--- a/file.c
+++ b/file.c
@@ -92,6 +92,8 @@ int flock(int, int);
 #define chown(p, o, g)	rb_w32_uchown((p), (o), (g))
 #undef utime
 #define utime(p, t)	rb_w32_uutime((p), (t))
+#undef lutime
+#define lutime(p, t)	rb_w32_luutime((p), (t))
 #undef link
 #define link(f, t)	rb_w32_ulink((f), (t))
 #undef unlink
@@ -2206,11 +2208,23 @@ rb_file_s_lchown(int argc, VALUE *argv)
 #define rb_file_s_lchown rb_f_notimplement
 #endif
 
+typedef enum utime_mode utime_mode_t;
+
+enum utime_mode {
+    M_SYMLINK_FOLLOW = 0,
+    M_SYMLINK_NOFOLLOW
+};
+
 struct utime_args {
     const struct timespec* tsp;
     VALUE atime, mtime;
+    utime_mode_t mode;
 };
 
+#if !defined(FOLLOW_SYMLINK_P)
+# define FOLLOW_SYMLINK_P(x) ((x)->mode == M_SYMLINK_FOLLOW)
+#endif
+
 #if defined DOSISH || defined __CYGWIN__
 NORETURN(static void utime_failed(VALUE, const struct timespec *, VALUE, VALUE));
 
@@ -2252,7 +2266,7 @@ utime_failed(VALUE path, const struct timespec *tsp, VALUE atime, VALUE mtime)
 #if defined(HAVE_UTIMES)
 
 static void
-utime_internal(const char *path, VALUE pathv, void *arg)
+utime_helper(const char *path, VALUE pathv, void *arg)
 {
     struct utime_args *v = arg;
     const struct timespec *tsp = v->tsp;
@@ -2260,9 +2274,10 @@ utime_internal(const char *path, VALUE pathv, void *arg)
 
 #ifdef HAVE_UTIMENSAT
     static int try_utimensat = 1;
+    const int flags = FOLLOW_SYMLINK_P(v) ? 0 : AT_SYMLINK_NOFOLLOW;
 
     if (try_utimensat) {
-        if (utimensat(AT_FDCWD, path, tsp, 0) < 0) {
+        if (utimensat(AT_FDCWD, path, tsp, flags) < 0) {
             if (errno == ENOSYS) {
                 try_utimensat = 0;
                 goto no_utimensat;
@@ -2281,7 +2296,7 @@ no_utimensat:
         tvbuf[1].tv_usec = (int)(tsp[1].tv_nsec / 1000);
         tvp = tvbuf;
     }
-    if (utimes(path, tvp) < 0)
+    if ((FOLLOW_SYMLINK_P(v) ? utimes(path, tvp) : lutimes(path, tvp)) < 0)
 	utime_failed(pathv, tsp, v->atime, v->mtime);
 }
 
@@ -2295,7 +2310,7 @@ struct utimbuf {
 #endif
 
 static void
-utime_internal(const char *path, VALUE pathv, void *arg)
+utime_helper(const char *path, VALUE pathv, void *arg)
 {
     struct utime_args *v = arg;
     const struct timespec *tsp = v->tsp;
@@ -2305,23 +2320,14 @@ utime_internal(const char *path, VALUE pathv, void *arg)
         utbuf.modtime = tsp[1].tv_sec;
         utp = &utbuf;
     }
-    if (utime(path, utp) < 0)
+    if ((FOLLOW_SYMLINK_P(v) ? utimes(path, utp) : lutimes(path, utp)) < 0)
 	utime_failed(pathv, tsp, v->atime, v->mtime);
 }
 
 #endif
 
-/*
- * call-seq:
- *  File.utime(atime, mtime, file_name,...)   ->  integer
- *
- * Sets the access and modification times of each
- * named file to the first two arguments. Returns
- * the number of file names in the argument list.
- */
-
 static VALUE
-rb_file_s_utime(int argc, VALUE *argv)
+utime_internal(int argc, VALUE *argv, utime_mode_t mode)
 {
     VALUE rest;
     struct utime_args args;
@@ -2337,11 +2343,58 @@ rb_file_s_utime(int argc, VALUE *argv)
 	tsp[1] = rb_time_timespec(args.mtime);
     }
     args.tsp = tsp;
+#ifdef HAVE_LUTIMES
+    args.mode = mode;
+#else
+    args.mode = M_SYMLINK_FOLLOW;
+#endif
 
-    n = apply2files(utime_internal, rest, &args);
+    n = apply2files(utime_helper, rest, &args);
     return LONG2FIX(n);
 }
 
+/*
+ * call-seq:
+ *  File.utime(atime, mtime, file_name,...)   ->  integer
+ *
+ * Sets the access and modification times of each
+ * named file to the first two arguments. Returns
+ * the number of file names in the argument list.
+ * See also File::lutime.
+ */
+
+static inline VALUE
+rb_file_s_utime(int argc, VALUE *argv)
+{
+    return utime_internal(argc, argv, M_SYMLINK_FOLLOW);
+}
+
+#ifdef HAVE_LUTIMES
+/*
+ * call-seq:
+ *  File.lutime(atime, mtime, file_name,...)   ->  integer
+ *
+ * Equivalent to File::utime, but does not follow
+ * symbolic links (so it will change the +atime+
+ * and +mtime+ associated with the link, not the
+ * file referenced by the link). Often not available.
+ *
+ * See also File::utime.
+ */
+
+static VALUE
+rb_file_s_lutime(int argc, VALUE *argv)
+{
+    return utime_internal(argc, argv, M_SYMLINK_NOFOLLOW);
+}
+#else
+#define rb_file_s_lutime rb_f_notimplement
+#endif
+
+#if defined(FOLLOW_SYMLINK_P)
+# undef FOLLOW_SYMLINK_P
+#endif
+
 NORETURN(static void sys_fail2(VALUE,VALUE));
 static void
 sys_fail2(VALUE s1, VALUE s2)
@@ -5439,6 +5492,7 @@ Init_File(void)
     rb_define_singleton_method(rb_cFile, "ctime", rb_file_s_ctime, 1);
 
     rb_define_singleton_method(rb_cFile, "utime", rb_file_s_utime, -1);
+    rb_define_singleton_method(rb_cFile, "lutime", rb_file_s_lutime, -1);
     rb_define_singleton_method(rb_cFile, "chmod", rb_file_s_chmod, -1);
     rb_define_singleton_method(rb_cFile, "chown", rb_file_s_chown, -1);
     rb_define_singleton_method(rb_cFile, "lchmod", rb_file_s_lchmod, -1);
diff --git a/lib/fileutils.rb b/lib/fileutils.rb
index 67cc79f..32b9c74 100644
--- a/lib/fileutils.rb
+++ b/lib/fileutils.rb
@@ -1181,7 +1181,7 @@ private
 public
 
   #
-  # Options: noop verbose
+  # Options: noop verbose mtime nocreate nofollow
   #
   # Updates modification time (mtime) and access time (atime) of file(s) in
   # +list+.  Files are created if they don't exist.
@@ -1201,7 +1201,11 @@ public
     list.each do |path|
       created = nocreate
       begin
-        File.utime(t, t, path)
+        if File.symlink?(path) and options[:nofollow]
+          File.lutime(t, t, path)
+        else
+          File.utime(t, t, path)
+        end
       rescue Errno::ENOENT
         raise if created
         File.open(path, 'a') {
@@ -1213,7 +1217,7 @@ public
     end
   end
 
-  define_command('touch', :noop, :verbose, :mtime, :nocreate)
+  define_command('touch', :noop, :verbose, :mtime, :nocreate, :nofollow)
 
 private
 
@@ -1442,7 +1446,12 @@ private
 
     def copy_metadata(path)
       st = lstat()
-      if !st.symlink?
+      if st.symlink?
+        begin
+          File.lutime st.atime, st.mtime, path
+        rescue NotImplementedError
+        end
+      else
         File.utime st.atime, st.mtime, path
       end
       begin
diff --git a/test/ruby/test_file_exhaustive.rb b/test/ruby/test_file_exhaustive.rb
index 76cf615..5049b26 100644
--- a/test/ruby/test_file_exhaustive.rb
+++ b/test/ruby/test_file_exhaustive.rb
@@ -374,6 +374,19 @@ class TestFileExhaustive < Test::Unit::TestCase
     assert_equal(t + 2, File.mtime(@zerofile))
   end
 
+  def test_lutime
+    return unless @symlinkfile
+    t = Time.local(2000)
+    assert_equal(1, File.lutime(t + 1, t + 2, @symlinkfile))
+    assert_equal(2, File.lutime(t + 1, t + 2, @symlinkfile, @symlinkfile))
+    assert_equal(t + 1, File.lstat(@symlinkfile).atime)
+    assert_equal(t + 2, File.lstat(@symlinkfile).mtime)
+    assert_not_equal(t + 1, File.atime(@symlinkfile))
+    assert_not_equal(t + 2, File.mtime(@symlinkfile))
+    assert_raise(Errno::ENOENT) { File.lutime(t + 1, t + 2, @nofile) }
+  rescue NotImplementedError
+  end
+
   def test_hardlink
     return unless @hardlinkfile
     assert_equal("file", File.ftype(@hardlinkfile))
diff --git a/win32/win32.c b/win32/win32.c
index 7eaf1fc..ca80abb 100644
--- a/win32/win32.c
+++ b/win32/win32.c
@@ -6195,6 +6195,14 @@ rb_w32_write_console(uintptr_t strarg, int fd)
 }
 
 /* License: Ruby's */
+typedef enum utime_mode utime_mode_t;
+
+enum utime_mode {
+    M_SYMLINK_FOLLOW = 0,
+    M_SYMLINK_NOFOLLOW
+};
+
+/* License: Ruby's */
 static int
 unixtime_to_filetime(time_t time, FILETIME *ft)
 {
@@ -6208,7 +6216,7 @@ unixtime_to_filetime(time_t time, FILETIME *ft)
 
 /* License: Ruby's */
 static int
-wutime(const WCHAR *path, const struct utimbuf *times)
+wutime(const WCHAR *path, const struct utimbuf *times, utime_mode_t mode)
 {
     HANDLE hFile;
     FILETIME atime, mtime;
@@ -6232,12 +6240,17 @@ wutime(const WCHAR *path, const struct utimbuf *times)
 	mtime = atime;
     }
 
+    int flags = FILE_FLAG_BACKUP_SEMANTICS;
+
+    if (mode == M_SYMLINK_NOFOLLOW)
+        flags |= FILE_FLAG_OPEN_REPARSE_POINT;
+
     RUBY_CRITICAL({
 	const DWORD attr = GetFileAttributesW(path);
 	if (attr != (DWORD)-1 && (attr & FILE_ATTRIBUTE_READONLY))
 	    SetFileAttributesW(path, attr & ~FILE_ATTRIBUTE_READONLY);
 	hFile = CreateFileW(path, GENERIC_WRITE, 0, 0, OPEN_EXISTING,
-			    FILE_FLAG_BACKUP_SEMANTICS, 0);
+			    flags, 0);
 	if (hFile == INVALID_HANDLE_VALUE) {
 	    errno = map_errno(GetLastError());
 	    ret = -1;
@@ -6258,34 +6271,62 @@ wutime(const WCHAR *path, const struct utimbuf *times)
 
 /* License: Ruby's */
 int
-rb_w32_uutime(const char *path, const struct utimbuf *times)
+uutime_internal(const char *path, const struct utimbuf *time, utime_mode_t mode)
 {
     WCHAR *wpath;
     int ret;
 
     if (!(wpath = utf8_to_wstr(path, NULL)))
 	return -1;
-    ret = wutime(wpath, times);
+    ret = wutime(wpath, times, mode);
     free(wpath);
     return ret;
 }
 
 /* License: Ruby's */
 int
-rb_w32_utime(const char *path, const struct utimbuf *times)
+utime_internal(const char *path, const struct utimbuf *times, utime_mode_t mode)
 {
     WCHAR *wpath;
     int ret;
 
     if (!(wpath = filecp_to_wstr(path, NULL)))
 	return -1;
-    ret = wutime(wpath, times);
+    ret = wutime(wpath, times, mode);
     free(wpath);
     return ret;
 }
 
 /* License: Ruby's */
 int
+rb_w32_uutime(const char *path, const struct utimbuf *times)
+{
+    return uutime_internal(path, utimbuf, M_SYMLINK_FOLLOW);
+}
+
+/* License: Ruby's */
+int
+rb_w32_utime(const char *path, const struct utimbuf *times)
+{
+    return utime_internal(path, utimbuf, M_SYMLINK_FOLLOW);
+}
+
+/* License: Ruby's */
+int
+rb_w32_luutime(const char *path, const struct utimbuf *times)
+{
+    return uutime_internal(path, utimbuf, M_SYMLINK_NOFOLLOW);
+}
+
+/* License: Ruby's */
+int
+rb_w32_lutime(const char *path, const struct utimbuf *times)
+{
+    return utime_internal(path, utimbuf, M_SYMLINK_NOFOLLOW);
+}
+
+/* License: Ruby's */
+int
 rb_w32_uchdir(const char *path)
 {
     WCHAR *wpath;
