diff --git a/cheevos/cheevos.c b/cheevos/cheevos.c
index bfbffff2a8..08e8478f3b 100644
--- a/cheevos/cheevos.c
+++ b/cheevos/cheevos.c
@@ -92,6 +92,7 @@ static rcheevos_locals_t rcheevos_locals =
    {{0}},/* memory */
 #ifdef HAVE_THREADS
    CMD_EVENT_NONE, /* queued_command */
+   false, /* game_placard_requested */
 #endif
 #ifndef HAVE_RC_CLIENT
    "",   /* displayname */
@@ -134,6 +135,9 @@ rcheevos_locals_t* get_rcheevos_locals(void)
 Supporting functions.
 *****************************************************************************/
 
+#define CMD_CHEEVOS_NON_COMMAND -1
+static void rcheevos_show_game_placard(void);
+
 #ifndef CHEEVOS_VERBOSE
 void rcheevos_log(const char* fmt, ...)
 {
@@ -602,6 +606,39 @@ static void rcheevos_server_error(const char* api_name, const char* message)
       MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_ERROR);
 }
 
+static void rcheevos_server_disconnected()
+{
+   CHEEVOS_LOG(RCHEEVOS_TAG "Unable to communicate with RetroAchievements server\n");
+
+   /* always show message - even with widget. it helps the user understand what the widget is for */
+   {
+      const char* message = msg_hash_to_str(MENU_ENUM_LABEL_CHEEVOS_SERVER_DISCONNECTED);
+      runloop_msg_queue_push(message, 0, 3 * 60, false, NULL,
+         MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_WARNING);
+   }
+
+#if defined(HAVE_GFX_WIDGETS)
+   if (gfx_widgets_ready())
+      gfx_widget_set_cheevos_disconnect(true);
+#endif
+}
+
+static void rcheevos_server_reconnected()
+{
+   CHEEVOS_LOG(RCHEEVOS_TAG "All pending requests synced to RetroAchievements server\n");
+
+   {
+      const char* message = msg_hash_to_str(MENU_ENUM_LABEL_CHEEVOS_SERVER_RECONNECTED);
+      runloop_msg_queue_push(message, 0, 3 * 60, false, NULL,
+         MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_SUCCESS);
+   }
+
+#if defined(HAVE_GFX_WIDGETS)
+   if (gfx_widgets_ready())
+      gfx_widget_set_cheevos_disconnect(false);
+#endif
+}
+
 static void rcheevos_client_event_handler(const rc_client_event_t* event, rc_client_t* client)
 {
    switch (event->type)
@@ -657,10 +694,10 @@ static void rcheevos_client_event_handler(const rc_client_event_t* event, rc_cli
       rcheevos_server_error(event->server_error->api, event->server_error->error_message);
       break;
    case RC_CLIENT_EVENT_DISCONNECTED:
-      CHEEVOS_LOG(RCHEEVOS_TAG "Unable to communicate with RetroAchievements server");
+      rcheevos_server_disconnected();
       break;
    case RC_CLIENT_EVENT_RECONNECTED:
-      CHEEVOS_LOG(RCHEEVOS_TAG "All pending requests synced to RetroAchievements server");
+      rcheevos_server_reconnected();
       break;
    default:
 #ifndef NDEBUG
@@ -1202,11 +1239,14 @@ bool rcheevos_unload(void)
       /* Clean up after completed tasks */
       task_queue_check();
    }
-
-   rcheevos_locals.queued_command = CMD_EVENT_NONE;
  #endif
 #endif
 
+#ifdef HAVE_THREADS
+   rcheevos_locals.queued_command = CMD_EVENT_NONE;
+   rcheevos_locals.game_placard_requested = false;
+#endif
+
    if (rcheevos_locals.memory.count > 0)
       rc_libretro_memory_destroy(&rcheevos_locals.memory);
 
@@ -1866,8 +1906,16 @@ void rcheevos_test(void)
 #ifdef HAVE_THREADS
    if (rcheevos_locals.queued_command != CMD_EVENT_NONE)
    {
-      command_event(rcheevos_locals.queued_command, NULL);
+      if (rcheevos_locals.queued_command != CMD_CHEEVOS_NON_COMMAND)
+         command_event(rcheevos_locals.queued_command, NULL);
+
       rcheevos_locals.queued_command = CMD_EVENT_NONE;
+
+      if (rcheevos_locals.game_placard_requested)
+      {
+         rcheevos_locals.game_placard_requested = false;
+         rcheevos_show_game_placard();
+      }
    }
 #endif
 
@@ -2414,7 +2462,16 @@ static void rcheevos_client_load_game_callback(int result,
       rc_client_set_read_memory_function(client, rcheevos_client_read_memory);
    }
 
-   rcheevos_show_game_placard();
+#ifdef HAVE_THREADS
+   if (!task_is_on_main_thread())
+   {
+      /* have to "schedule" this. game image should not be loaded on background thread */
+      rcheevos_locals.queued_command = CMD_CHEEVOS_NON_COMMAND;
+      rcheevos_locals.game_placard_requested = true;
+   }
+   else
+      rcheevos_show_game_placard();
+#endif
 
    rcheevos_finalize_game_load(client);
 
diff --git a/cheevos/cheevos_locals.h b/cheevos/cheevos_locals.h
index 72077cbab2..708f166502 100644
--- a/cheevos/cheevos_locals.h
+++ b/cheevos/cheevos_locals.h
@@ -201,6 +201,7 @@ typedef struct rcheevos_locals_t
 
 #ifdef HAVE_THREADS
    enum event_command queued_command; /* action queued by background thread to be run on main thread */
+   bool game_placard_requested;       /* request to display game placard */
 #endif
 
 #ifndef HAVE_RC_CLIENT
diff --git a/cheevos/cheevos_menu.c b/cheevos/cheevos_menu.c
index e92d1a7013..4ff901ef4c 100644
--- a/cheevos/cheevos_menu.c
+++ b/cheevos/cheevos_menu.c
@@ -28,6 +28,7 @@
 
 #include "../deps/rcheevos/include/rc_runtime_types.h"
 #include "../deps/rcheevos/include/rc_api_runtime.h"
+#include "../deps/rcheevos/src/rc_client_internal.h"
 
 #include "../menu/menu_driver.h"
 #include "../menu/menu_entries.h"
@@ -295,6 +296,15 @@ void rcheevos_menu_populate(void* data)
    rcheevos_menu_reset_badges();
    rcheevos_locals->menuitem_count = 0;
 
+   if (rcheevos_locals->client->state.disconnect)
+   {
+      menu_entries_append(info->list,
+         msg_hash_to_str(MENU_ENUM_LABEL_VALUE_ACHIEVEMENT_SERVER_UNREACHABLE),
+         msg_hash_to_str(MENU_ENUM_SUBLABEL_ACHIEVEMENT_SERVER_UNREACHABLE),
+         MENU_ENUM_LABEL_ACHIEVEMENT_SERVER_UNREACHABLE,
+         MENU_INFO_ACHIEVEMENTS_SERVER_UNREACHABLE, 0, 0, NULL);
+   }
+
    if (game && game->id != 0)
    {
       /* first menu item is the Pause/Resume Hardcore option (unless hardcore is completely disabled) */
@@ -466,7 +476,11 @@ uintptr_t rcheevos_get_badge_texture(const char* badge, bool locked, bool downlo
       return 0;
 
    /* OpenGL driver crashes if gfx_display_reset_textures_list is called on a background thread */
-   retro_assert(task_is_on_main_thread());
+   if (!task_is_on_main_thread())
+   {
+      CHEEVOS_ERR(RCHEEVOS_TAG "attempt to load badge %s from background thread", badge);
+      retro_assert(task_is_on_main_thread());
+   }
 
    snprintf(badge_file, sizeof(badge_file), "%s%s%s", badge,
       locked ? "_lock" : "", FILE_PATH_PNG_EXTENSION);
diff --git a/gfx/gfx_widgets.h b/gfx/gfx_widgets.h
index f61e456b7a..91ed42f630 100644
--- a/gfx/gfx_widgets.h
+++ b/gfx/gfx_widgets.h
@@ -384,6 +384,7 @@ void gfx_widgets_clear_leaderboard_displays(void);
 void gfx_widgets_set_challenge_display(unsigned id, const char* badge);
 void gfx_widgets_clear_challenge_displays(void);
 void gfx_widget_set_achievement_progress(const char* badge, const char* progress);
+void gfx_widget_set_cheevos_disconnect(bool visible);
 #endif
 
 /* TODO/FIXME/WARNING: Not thread safe! */
diff --git a/gfx/widgets/gfx_widget_leaderboard_display.c b/gfx/widgets/gfx_widget_leaderboard_display.c
index 5fabf5bc34..6383a30108 100644
--- a/gfx/widgets/gfx_widget_leaderboard_display.c
+++ b/gfx/widgets/gfx_widget_leaderboard_display.c
@@ -51,6 +51,8 @@ struct progress_tracker_info
 
 #define CHEEVO_LBOARD_FIRST_FIXED_CHAR 0x2D /* -./0123456789: */
 #define CHEEVO_LBOARD_LAST_FIXED_CHAR 0x3A
+
+/* TODO: rename; this file handles all achievement tracker information, not just leaderboards */
 struct gfx_widget_leaderboard_display_state
 {
 #ifdef HAVE_THREADS
@@ -64,6 +66,7 @@ struct gfx_widget_leaderboard_display_state
    unsigned challenge_count;
    uint16_t char_width[CHEEVO_LBOARD_LAST_FIXED_CHAR - CHEEVO_LBOARD_FIRST_FIXED_CHAR + 1];
    uint16_t fixed_char_width;
+   bool disconnected;
 };
 
 typedef struct gfx_widget_leaderboard_display_state gfx_widget_leaderboard_display_state_t;
@@ -109,7 +112,10 @@ static void gfx_widget_leaderboard_display_frame(void* data, void* userdata)
    gfx_widget_leaderboard_display_state_t *state = &p_w_leaderboard_display_st;
 
    /* if there's nothing to display, just bail */
-   if (state->tracker_count == 0 && state->challenge_count == 0 && state->progress_tracker.show_until == 0)
+   if (state->tracker_count == 0 &&
+       state->challenge_count == 0 &&
+       state->progress_tracker.show_until == 0 &&
+       !state->disconnected)
       return;
 
 #ifdef HAVE_THREADS
@@ -335,6 +341,38 @@ static void gfx_widget_leaderboard_display_frame(void* data, void* userdata)
                   TEXT_COLOR_INFO, TEXT_ALIGN_LEFT, true);
          }
       }
+
+      if (state->disconnected)
+      {
+         const char* disconnected_text = "! RA !";
+         const unsigned disconnect_widget_width = font_driver_get_message_width(
+            state->dispwidget_ptr->gfx_widget_fonts.msg_queue.font,
+            disconnected_text, 0, 1) + CHEEVO_LBOARD_DISPLAY_PADDING * 2;
+         const unsigned disconnect_widget_height =
+            p_dispwidget->gfx_widget_fonts.msg_queue.line_height + (CHEEVO_LBOARD_DISPLAY_PADDING - 1) * 2;
+         x = video_width - disconnect_widget_width - spacing;
+         y -= disconnect_widget_height + spacing;
+
+         /* Backdrop */
+         gfx_display_draw_quad(
+            p_disp,
+            video_info->userdata,
+            video_width, video_height,
+            (int)x, (int)y, disconnect_widget_width, disconnect_widget_height,
+            video_width, video_height,
+            p_dispwidget->backdrop_orig,
+            NULL);
+
+         /* Text */
+         char_x = (float)(x + CHEEVO_LBOARD_DISPLAY_PADDING);
+         char_y = (float)(y + disconnect_widget_height - (CHEEVO_LBOARD_DISPLAY_PADDING - 1)
+            - p_dispwidget->gfx_widget_fonts.msg_queue.line_descender);
+
+         gfx_widgets_draw_text(&p_dispwidget->gfx_widget_fonts.msg_queue,
+            disconnected_text, char_x, char_y,
+            video_width, video_height,
+            TEXT_COLOR_INFO, TEXT_ALIGN_LEFT, true);
+      }
    }
 
 #ifdef HAVE_THREADS
@@ -542,6 +580,13 @@ void gfx_widget_set_achievement_progress(const char* badge, const char* progress
       video_driver_texture_unload(&old_badge_id);
 }
 
+void gfx_widget_set_cheevos_disconnect(bool value)
+{
+   gfx_widget_leaderboard_display_state_t* state = &p_w_leaderboard_display_st;
+   state->disconnected = value;
+}
+
+
 const gfx_widget_t gfx_widget_leaderboard_display = {
    &gfx_widget_leaderboard_display_init,
    &gfx_widget_leaderboard_display_free,
diff --git a/intl/msg_hash_us.h b/intl/msg_hash_us.h
index 7bab0f2ba5..7bd398d13d 100644
--- a/intl/msg_hash_us.h
+++ b/intl/msg_hash_us.h
@@ -9691,6 +9691,22 @@ MSG_HASH(
    MENU_ENUM_SUBLABEL_ACHIEVEMENT_RESUME,
    "Resume achievements hardcore mode for the current session. This action will disable cheats, rewind, slow-motion, and loading save states and reset the current game."
    )
+MSG_HASH(
+   MENU_ENUM_LABEL_VALUE_ACHIEVEMENT_SERVER_UNREACHABLE,
+   "RetroAchievements server is unreachable"
+)
+MSG_HASH(
+   MENU_ENUM_SUBLABEL_ACHIEVEMENT_SERVER_UNREACHABLE,
+   "One or more achievement unlocks did not make it to the server. The unlocks will be retried as long as you leave the app open."
+)
+MSG_HASH(
+   MENU_ENUM_LABEL_CHEEVOS_SERVER_DISCONNECTED,
+   "RetroAchievements server is unreachable. Will retry until successful or the app is closed."
+)
+MSG_HASH(
+   MENU_ENUM_LABEL_CHEEVOS_SERVER_RECONNECTED,
+   "All pending requests have succesfully been synced to the RetroAchievements server."
+)
 MSG_HASH(
    MENU_ENUM_LABEL_VALUE_NOT_LOGGED_IN,
    "Not logged in"
diff --git a/menu/cbs/menu_cbs_sublabel.c b/menu/cbs/menu_cbs_sublabel.c
index 20e4ae0eaa..520595fd5b 100644
--- a/menu/cbs/menu_cbs_sublabel.c
+++ b/menu/cbs/menu_cbs_sublabel.c
@@ -301,6 +301,7 @@ DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_information_list_list,         MENU_
 DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_achievement_list,              MENU_ENUM_SUBLABEL_ACHIEVEMENT_LIST)
 DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_achievement_pause_cancel,      MENU_ENUM_SUBLABEL_ACHIEVEMENT_PAUSE_CANCEL)
 DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_achievement_resume_cancel,     MENU_ENUM_SUBLABEL_ACHIEVEMENT_RESUME_CANCEL)
+DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_achievement_server_unreachable,MENU_ENUM_SUBLABEL_ACHIEVEMENT_SERVER_UNREACHABLE)
 DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_cheevos_enable,                MENU_ENUM_SUBLABEL_CHEEVOS_ENABLE)
 DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_cheevos_test_unofficial,       MENU_ENUM_SUBLABEL_CHEEVOS_TEST_UNOFFICIAL)
 DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_cheevos_hardcore_mode_enable,  MENU_ENUM_SUBLABEL_CHEEVOS_HARDCORE_MODE_ENABLE)
@@ -4564,6 +4565,9 @@ int menu_cbs_init_bind_sublabel(menu_file_list_cbs_t *cbs,
          case MENU_ENUM_LABEL_ACHIEVEMENT_RESUME_CANCEL:
             BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_achievement_resume_cancel);
             break;
+         case MENU_ENUM_LABEL_ACHIEVEMENT_SERVER_UNREACHABLE:
+            BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_achievement_server_unreachable);
+            break;
          case MENU_ENUM_LABEL_CHEEVOS_UNLOCKED_ENTRY:
          case MENU_ENUM_LABEL_CHEEVOS_UNLOCKED_ENTRY_HARDCORE:
          case MENU_ENUM_LABEL_CHEEVOS_LOCKED_ENTRY:
diff --git a/menu/drivers/ozone.c b/menu/drivers/ozone.c
index f8d5287ef9..6e46c31c0d 100644
--- a/menu/drivers/ozone.c
+++ b/menu/drivers/ozone.c
@@ -2290,6 +2290,8 @@ static uintptr_t ozone_entries_icon_get_texture(
          return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_RELOAD];
       case MENU_SETTING_ACTION_PAUSE_ACHIEVEMENTS:
          return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_PAUSE];
+      case MENU_INFO_ACHIEVEMENTS_SERVER_UNREACHABLE:
+         return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_NETWORK];
       case MENU_SETTING_GROUP:
          return ozone->icons_textures[OZONE_ENTRIES_ICONS_TEXTURE_SETTING];
       case MENU_SET_SCREEN_BRIGHTNESS:
diff --git a/menu/drivers/xmb.c b/menu/drivers/xmb.c
index 09ffe50f04..816dbf0bc5 100644
--- a/menu/drivers/xmb.c
+++ b/menu/drivers/xmb.c
@@ -3465,6 +3465,8 @@ static uintptr_t xmb_icon_get_id(xmb_handle_t *xmb,
          return xmb->textures.list[XMB_TEXTURE_RELOAD];
       case MENU_SETTING_ACTION_PAUSE_ACHIEVEMENTS:
          return xmb->textures.list[XMB_TEXTURE_PAUSE];
+      case MENU_ENUM_LABEL_ACHIEVEMENT_SERVER_UNREACHABLE:
+         return xmb->textures.list[XMB_TEXTURE_NETWORK];
       case MENU_SET_SCREEN_BRIGHTNESS:
          return xmb->textures.list[XMB_TEXTURE_BRIGHTNESS];
       case MENU_SETTING_GROUP:
diff --git a/menu/menu_driver.h b/menu/menu_driver.h
index 98803360f1..e56be9fdd7 100644
--- a/menu/menu_driver.h
+++ b/menu/menu_driver.h
@@ -169,6 +169,7 @@ enum menu_settings_type
    MENU_SETTING_HORIZONTAL_MENU,
    MENU_SETTING_ACTION_PAUSE_ACHIEVEMENTS,
    MENU_SETTING_ACTION_RESUME_ACHIEVEMENTS,
+   MENU_INFO_ACHIEVEMENTS_SERVER_UNREACHABLE,
    MENU_SETTING_PLAYLIST_MANAGER_DEFAULT_CORE,
    MENU_SETTING_PLAYLIST_MANAGER_LABEL_DISPLAY_MODE,
    MENU_SETTING_PLAYLIST_MANAGER_RIGHT_THUMBNAIL_MODE,
diff --git a/msg_hash.h b/msg_hash.h
index 8c708a109e..9b7270a8fd 100644
--- a/msg_hash.h
+++ b/msg_hash.h
@@ -2926,6 +2926,9 @@ enum msg_hash_enums
    MENU_LABEL(ACHIEVEMENT_RESUME_CANCEL),
    MENU_LABEL(ACHIEVEMENT_PAUSE),
    MENU_LABEL(ACHIEVEMENT_RESUME),
+   MENU_LABEL(ACHIEVEMENT_SERVER_UNREACHABLE),
+   MENU_LABEL(CHEEVOS_SERVER_DISCONNECTED),
+   MENU_LABEL(CHEEVOS_SERVER_RECONNECTED),
    MENU_LABEL(CORE_INFORMATION),
    MENU_LABEL(DISC_INFORMATION),
    MENU_LABEL(CORE_LOCK),