diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini
index 07ca9f8409..bdc42480e4 100644
--- a/custom/conf/app.example.ini
+++ b/custom/conf/app.example.ini
@@ -1925,6 +1925,19 @@ PATH =
 ;SCHEDULE = @every 168h
 ;OLDER_THAN = 8760h
 
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Check for new Gitea versions
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;[cron.update_checker]
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;ENABLED = false
+;RUN_AT_START = false
+;ENABLE_SUCCESS_NOTICE = false
+;SCHEDULE = @every 168h
+;HTTP_ENDPOINT = https://dl.gitea.io/gitea/version.json
+
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 ;; Git Operation timeout in seconds
diff --git a/docs/content/doc/advanced/config-cheat-sheet.en-us.md b/docs/content/doc/advanced/config-cheat-sheet.en-us.md
index f02d8cbc37..251f6bd51a 100644
--- a/docs/content/doc/advanced/config-cheat-sheet.en-us.md
+++ b/docs/content/doc/advanced/config-cheat-sheet.en-us.md
@@ -23,8 +23,8 @@ or any corresponding location. When installing from a distribution, this will
 typically be found at `/etc/gitea/conf/app.ini`.
 
 The defaults provided here are best-effort (not built automatically). They are
-accurately recorded in [app.example.ini](https://github.com/go-gitea/gitea/blob/master/custom/conf/app.example.ini)
-(s/master/\<tag|release\>). Any string in the format `%(X)s` is a feature powered
+accurately recorded in [app.example.ini](https://github.com/go-gitea/gitea/blob/main/custom/conf/app.example.ini)
+(s/main/\<tag|release\>). Any string in the format `%(X)s` is a feature powered
 by [ini](https://github.com/go-ini/ini/#recursive-values), for reading values recursively.
 
 Values containing `#` or `;` must be quoted using `` ` `` or `"""`.
@@ -824,9 +824,16 @@ NB: You must have `DISABLE_ROUTER_LOG` set to `false` for this option to take ef
 - `ENABLED`: **false**: Enable service.
 - `RUN_AT_START`: **false**: Run tasks at start up time (if ENABLED).
 - `NO_SUCCESS_NOTICE`: **false**: Set to true to switch off success notices.
-- `SCHEDULE`: **@every 128h**: Cron syntax for scheduling a work, e.g. `@every 128h`.
+- `SCHEDULE`: **@every 168h**: Cron syntax to set how often to check.
 - `OLDER_THAN`: **@every 8760h**: any action older than this expression will be deleted from database, suggest using `8760h` (1 year) because that's the max length of heatmap.
 
+#### Cron -  Check for new Gitea versions ('cron.update_checker')
+- `ENABLED`: **false**: Enable service.
+- `RUN_AT_START`: **false**: Run tasks at start up time (if ENABLED).
+- `ENABLE_SUCCESS_NOTICE`: **true**: Set to false to switch off success notices.
+- `SCHEDULE`: **@every 168h**: Cron syntax for scheduling a work, e.g. `@every 168h`.
+- `HTTP_ENDPOINT`: **https://dl.gitea.io/gitea/version.json**: the endpoint that Gitea will check for newer versions
+
 ## Git (`git`)
 
 - `PATH`: **""**: The path of git executable. If empty, Gitea searches through the PATH environment.
diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go
index ef0c071417..3a41cf8891 100644
--- a/models/migrations/migrations.go
+++ b/models/migrations/migrations.go
@@ -350,6 +350,8 @@ var migrations = []Migration{
 	NewMigration("Add renamed_branch table", addRenamedBranchTable),
 	// v198 -> v199
 	NewMigration("Add issue content history table", addTableIssueContentHistory),
+	// v199 -> v200
+	NewMigration("Add remote version table", addRemoteVersionTable),
 }
 
 // GetCurrentDBVersion returns the current db version
diff --git a/models/migrations/v199.go b/models/migrations/v199.go
new file mode 100644
index 0000000000..64b21172c1
--- /dev/null
+++ b/models/migrations/v199.go
@@ -0,0 +1,23 @@
+// Copyright 2021 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package migrations
+
+import (
+	"fmt"
+
+	"xorm.io/xorm"
+)
+
+func addRemoteVersionTable(x *xorm.Engine) error {
+	type RemoteVersion struct {
+		ID      int64  `xorm:"pk autoincr"`
+		Version string `xorm:"VARCHAR(50)"`
+	}
+
+	if err := x.Sync2(new(RemoteVersion)); err != nil {
+		return fmt.Errorf("Sync2: %v", err)
+	}
+	return nil
+}
diff --git a/models/update_checker.go b/models/update_checker.go
new file mode 100644
index 0000000000..5b4fce69ec
--- /dev/null
+++ b/models/update_checker.go
@@ -0,0 +1,121 @@
+// Copyright 2021 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package models
+
+import (
+	"encoding/json"
+	"fmt"
+	"io/ioutil"
+	"net/http"
+
+	"code.gitea.io/gitea/models/db"
+	"code.gitea.io/gitea/modules/proxy"
+	"code.gitea.io/gitea/modules/setting"
+
+	"github.com/hashicorp/go-version"
+)
+
+// RemoteVersion stores the remote version from the JSON endpoint
+type RemoteVersion struct {
+	ID      int64  `xorm:"pk autoincr"`
+	Version string `xorm:"VARCHAR(50)"`
+}
+
+func init() {
+	db.RegisterModel(new(RemoteVersion))
+}
+
+// GiteaUpdateChecker returns error when new version of Gitea is available
+func GiteaUpdateChecker(httpEndpoint string) error {
+	httpClient := &http.Client{
+		Transport: &http.Transport{
+			Proxy: proxy.Proxy(),
+		},
+	}
+
+	req, err := http.NewRequest("GET", httpEndpoint, nil)
+	if err != nil {
+		return err
+	}
+	resp, err := httpClient.Do(req)
+	if err != nil {
+		return err
+	}
+	defer resp.Body.Close()
+	body, err := ioutil.ReadAll(resp.Body)
+	if err != nil {
+		return err
+	}
+
+	type v struct {
+		Latest struct {
+			Version string `json:"version"`
+		} `json:"latest"`
+	}
+	ver := v{}
+	err = json.Unmarshal(body, &ver)
+	if err != nil {
+		return err
+	}
+
+	return UpdateRemoteVersion(ver.Latest.Version)
+
+}
+
+// UpdateRemoteVersion updates the latest available version of Gitea
+func UpdateRemoteVersion(version string) (err error) {
+	sess := db.NewSession(db.DefaultContext)
+	defer sess.Close()
+	if err = sess.Begin(); err != nil {
+		return err
+	}
+
+	currentVersion := &RemoteVersion{ID: 1}
+	has, err := sess.Get(currentVersion)
+	if err != nil {
+		return fmt.Errorf("get: %v", err)
+	} else if !has {
+		currentVersion.ID = 1
+		currentVersion.Version = version
+
+		if _, err = sess.InsertOne(currentVersion); err != nil {
+			return fmt.Errorf("insert: %v", err)
+		}
+		return nil
+	}
+
+	if _, err = sess.Update(&RemoteVersion{ID: 1, Version: version}); err != nil {
+		return err
+	}
+
+	return sess.Commit()
+}
+
+// GetRemoteVersion returns the current remote version (or currently installed verson if fail to fetch from DB)
+func GetRemoteVersion() string {
+	e := db.GetEngine(db.DefaultContext)
+	v := &RemoteVersion{ID: 1}
+	_, err := e.Get(&v)
+	if err != nil {
+		// return current version if fail to fetch from DB
+		return setting.AppVer
+	}
+	return v.Version
+}
+
+// GetNeedUpdate returns true whether a newer version of Gitea is available
+func GetNeedUpdate() bool {
+	curVer, err := version.NewVersion(setting.AppVer)
+	if err != nil {
+		// return false to fail silently
+		return false
+	}
+	remoteVer, err := version.NewVersion(GetRemoteVersion())
+	if err != nil {
+		// return false to fail silently
+		return false
+	}
+	return curVer.LessThan(remoteVer)
+}
diff --git a/modules/cron/tasks_extended.go b/modules/cron/tasks_extended.go
index 680f83e50c..6645e71d2c 100644
--- a/modules/cron/tasks_extended.go
+++ b/modules/cron/tasks_extended.go
@@ -131,6 +131,24 @@ func registerDeleteOldActions() {
 	})
 }
 
+func registerUpdateGiteaChecker() {
+	type UpdateCheckerConfig struct {
+		BaseConfig
+		HTTPEndpoint string
+	}
+	RegisterTaskFatal("update_checker", &UpdateCheckerConfig{
+		BaseConfig: BaseConfig{
+			Enabled:    true,
+			RunAtStart: false,
+			Schedule:   "@every 168h",
+		},
+		HTTPEndpoint: "https://dl.gitea.io/gitea/version.json",
+	}, func(ctx context.Context, _ *models.User, config Config) error {
+		updateCheckerConfig := config.(*UpdateCheckerConfig)
+		return models.GiteaUpdateChecker(updateCheckerConfig.HTTPEndpoint)
+	})
+}
+
 func initExtendedTasks() {
 	registerDeleteInactiveUsers()
 	registerDeleteRepositoryArchives()
@@ -142,4 +160,5 @@ func initExtendedTasks() {
 	registerDeleteMissingRepositories()
 	registerRemoveRandomAvatars()
 	registerDeleteOldActions()
+	registerUpdateGiteaChecker()
 }
diff --git a/routers/web/admin/admin.go b/routers/web/admin/admin.go
index ce177ea090..ca5b157523 100644
--- a/routers/web/admin/admin.go
+++ b/routers/web/admin/admin.go
@@ -125,6 +125,8 @@ func Dashboard(ctx *context.Context) {
 	ctx.Data["PageIsAdmin"] = true
 	ctx.Data["PageIsAdminDashboard"] = true
 	ctx.Data["Stats"] = models.GetStatistic()
+	ctx.Data["NeedUpdate"] = models.GetNeedUpdate()
+	ctx.Data["RemoteVersion"] = models.GetRemoteVersion()
 	// FIXME: update periodically
 	updateSystemStatus()
 	ctx.Data["SysStatus"] = sysStatus
diff --git a/templates/admin/dashboard.tmpl b/templates/admin/dashboard.tmpl
index de01f95eab..79f031882e 100644
--- a/templates/admin/dashboard.tmpl
+++ b/templates/admin/dashboard.tmpl
@@ -3,6 +3,11 @@
 	{{template "admin/navbar" .}}
 	<div class="ui container">
 		{{template "base/alert" .}}
+		{{if .NeedUpdate}}
+			<div class="ui negative message flash-error">
+				<p>"Gitea {{.RemoteVersion | Str2html}} is now available, you are running {{.AppVer | Str2html}}. Check the <a href="https://blog.gitea.io">blog</a> for more details.</p>
+			</div>
+		{{end}}
 		<h4 class="ui top attached header">
 			{{.i18n.Tr "admin.dashboard.statistic"}}
 		</h4>