diff --git a/security.php b/security.php
index 05424695c8..1c47a4f107 100644
--- a/security.php
+++ b/security.php
@@ -16,6 +16,10 @@
require_once __DIR__ . '/security/login-error.php';
require_once __DIR__ . '/security/password.php';
+if ( defined( 'VIP_USER_LAST_SEEN_ENABLED' ) ) {
+ require_once __DIR__ . '/security/user-last-seen.php';
+}
+
use Automattic\VIP\Utils\Context;
define( 'CACHE_GROUP_LOGIN_LIMIT', 'vip_login_limit' );
diff --git a/security/user-last-seen.php b/security/user-last-seen.php
new file mode 100644
index 0000000000..8a3fbd2f49
--- /dev/null
+++ b/security/user-last-seen.php
@@ -0,0 +1,60 @@
+ID;
+
+ try {
+ if ( wp_cache_get( $cache_key ) ) {
+ return;
+ }
+
+ update_user_meta( $current_user->ID, 'vip_last_seen', time() );
+
+ wp_cache_set( $cache_key, true, null, LAST_SEEN_UPDATE_USER_META_CACHE_TTL );
+ } catch ( \Exception $e ) {
+ trigger_error(
+ sprintf( 'failed to update user last seen meta', esc_html( $e->getMessage() ) ),
+ E_USER_WARNING
+ );
+ }
+}
+
+function users_columns ($cols) {
+ $cols['last_seen'] = __( 'Last seen' ) ;
+ return $cols;
+}
+
+function users_custom_column ( $default, $column_name, $user_id ) {
+ if ( 'last_seen' == $column_name ) {
+ $last_seen_timestamp = get_user_meta( $user_id, 'vip_last_seen', true );
+
+ if ( $last_seen_timestamp ) {
+ $formatted_date = sprintf(
+ __( '%1$s at %2$s' ),
+ date_i18n( get_option('date_format'), $last_seen_timestamp ),
+ date_i18n( get_option('time_format'), $last_seen_timestamp )
+ );
+
+ if ( defined( 'VIP_CONSIDER_USERS_INACTIVE_AFTER_DAYS' ) ) {
+ $diff = ( new DateTime( sprintf('@%s', $last_seen_timestamp ) ) )->diff( new DateTime() );
+
+ if ( $diff->days >= constant( 'VIP_CONSIDER_USERS_INACTIVE_AFTER_DAYS' ) ) {
+ return sprintf( '%s', esc_html__( $formatted_date ) );
+ }
+ }
+
+ return sprintf( '%s', esc_html__( $formatted_date ) );
+ }
+ }
+
+ return $default;
+}