performance issue on using ip2location for group of ips
Please qualify all columns with what table they are in.  Meanwhile, I will guess that ip_from and ip_to are in ip_city.

A problem is that the tables are over-normalized.  ip_city -> cities -> country instead of having a single table slows down the query.

But the main problem is that there is no good index for ip between ip_from AND ip_to.  Any index with ip_from and/or ip_to can, at best, limit the effort down to scanning half the table.  That's O(N).  To get to O(1), see http://mysql.rjweb.org/doc.php/ipranges .  Sorry, it is rather involved.

A first cut at improving your existing schema:

CREATE TABLE `cities` (
  `id` MEDIUMINT UNSIGNED NOT NULL,
  `name` varchar(50) NOT NULL,
  `country_code` CHAR(2) CHARACTER SET ascii NOT NULL,
  `latitude` FLOAT DEFAULT NULL,
  `longitude` FLOAT DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

CREATE TABLE `ips` (
  `ip` varchar(39) CHARACTER SET ascii NOT NULL,
  ip_packed BINARY(16) NOT NULL
  `user_id` int(10) UNSIGNED NOT NULL,
  `created_at` timestamp NULL DEFAULT NULL
  PRIMARY KEY(ip)
) ENGINE=InnoDB DEFAULT CHARSET=ascii;

CREATE TABLE `ip_city` (
  `ip_from` BINARY(16) DEFAULT NULL,
  `ip_to` BINARY(16) DEFAULT NULL,
  `city_id` int(11) UNSIGNED NOT NULL,
  PRIMARY KEY(ip_from, ip_to),
  INDEX(city_id)  -- if desired
) ENGINE=InnoDB DEFAULT;


That included getting rid if id when there were good 'natural' PKs, handling IPv6, using standard 2-letter country_codes, tossing the country table, using ascii where appropriate, etc.

See my link for further discussion, including why FLOAT is better.