How to effectively stop RemoteEvent spam

If you’re worried about people spamming remotes and don’t know any methods to combat this, this tutorial is for you! You should always have some sort of check on your remotes to validate each request. In this tutorial, you’ll learn how to stop RemoteEvent spam.

Note that this is just my way of doing this, so there are probably a lot of other methods people can do (for example, using os.time()). More or less though, most methods are wrapped around the same concept: Tracking time since the last time the player fired the remote

Using tick()

tick() is always a moving value, it’s the current number of seconds from the epoch (Jan 1 1970). But when you set something to tick(), the value of that becomes tick() at the current time and doesn’t move anymore. I used to be really confused about this and thought it still moved. So, knowing that we could so something like this:

local var = tick() -- this becomes tick()'s current value at the time the variable is being set to it, and doesn't move

wait(3) -- wait for 3 seconds to pass, tick() would now be 3 seconds more because it's always moving

print(tick() - var) --> would be about 3

Implementing with RemoteEvents

Using the tick() method, we could now use this to act as a cooldown time for each player firing the remote.

When you make a game on ROBLOX with FE, you always need to be validating requests from the client on the server. So, obviously this is being done in a server script, where the server listens for the RemoteEvent.

local playerTable = {} -- create an empty table to store an index for everyone in
local someRemoteEvent = game.ReplicatedStorage.RemoteEvent -- an example event
local cooldownTime = 4 -- used 4 seconds as an example

game.Players.PlayerAdded:Connect(function(player)
    if not playerTable[player.Name] then -- if there isn't an entry for the player already
        playerTable[player.Name] = tick() -- Create a new entry in the table for each new player, and make the index their name so we can access it later
    end
end

game.Players.PlayerRemoving:Connect(function(player)
    if playerTable[player.Name] then -- if there's an entry of the player in the table
        playerTable[player.Name] = nil -- remove it from the table
    end
end

someRemoteEvent.OnServerEvent:Connect(function(player)
    if tick() - playerTable[player.Name] > cooldownTime then -- If the time is greater than the cooldown time (so if more than 4 seconds have passed since the last time you set the player's index in the playerTable to tick()
        playerTable[player.Name] = tick() -- set it back to tick() so it resets
        -- run your code
    end
end

Here, we basically track the time since the last time the player fired the remote by storing it in the table, and then check if the time passed since then is greater than the cooldown time. If it is, we grant access to the remote and set the index back to tick() which resets it, because what that means is that you’re recording the time when the player fires the remote.

Conclusion

Just had to make a quick tutorial on this, so if it becomes of use to you, feel free to leave a like. Thanks for reading and have a good day!

66 Likes

Thank you for this helpful tutorial, I’m certain that this will help people secure their RemoteEvents way more, we absolutely needed a tutorial like this to teach people how to secure their RemoteEvents from exploiters that keep spamming them to possibly lag out the server and just for more security in general.

However, you could also do this with os.time() and time() so it’s not just tick() that could do this, I will try to explain all of them so people could understand how they work.

If you take a look at os.time() in the devhub you will see that they are very similar:

os.time()

And here’s a look at tick() in the devhub too:

tick()

The only difference that I see between tick() and os.time() is that tick() returns the time based on your current local session’s computer while os.time() isn’t based on your local session’s computer and os.time() offers more options for customization while tick() doesn’t offer any form of customization.

So this could certainly work with os.time() too but you need to learn how it works but it functions differently from how tick() works but it’s not that different, you should add the option to use os.time() too since you can use both and not just tick()

And also another alternative would be time() since it will be more accurate than tick() and os.time() because it returns the time in seconds since the game started running so it will be easier to use than both, however just like tick() it doesn’t give options for customization like os.time() does.

Here’s what the devhub says about time():

time()

So all of this means that we can also use os.time() and time() to do this too, all we need to do is replace all the tick() in the script and put os.time() or time() instead but you should be aware that you can customize how the time is returned by os.time().

I hope that my post was informative to everyone and severly helped improve this tutorial, you should mention these functions somewhere in the post so others know that they can do this in other ways and not just be stuck with tick(), feel free to correct me about anything wrong. :slightly_smiling_face:

6 Likes

Yup, exactly.
As a said, it’s all wrapped around the same concept of tracking time.

But, I think tick() would be the best option, because its the easiest for making your cooldown times with up to like 6-7 decimals because that’s tick()’s precision, meanwhile os.time() returns an integer, so you’d have to do some weird math there to try and get it to a decimal. Also, in this case, the actual time it’s returning doesn’t really matter, all you need is a moving time-based value, based on your local session’s computer or not. If there is an accuracy difference between tick(), os.time(), or time(), it’s probably too small of a difference and is negligible.

1 Like

A similar method that tends to be more clean is to just set the value nil after a set amount of time like so:

local remote = game.ReplicatedStorage.RemoteEvent

local tab = {}
local cooldown = 4
remote.OnServerEvent:Connect(function(player)
	if not tab[player.UserId] then
		tab[player.UserId] = true
		-- do whatever you want, but you might
		-- want to wrap it in a pcall just in case
		task.wait(cooldown)
		tab[player.UserId] = nil
	end
end)

It’s a method that works pretty well as long as the cool down isn’t too long.

7 Likes

This also works too! As I said, there’s many different ways to do this

You should use time() instead since tick() is going to be deprecated in the future and has problems of possibly being a little off the actual UNIX epoch.

This system is very easy to recreate on your own so I don’t see it being used much as a resource, you can also make it into a leaky bucket as well for stricter anti spam optionally.

Weird that many recommend os.time(), when its precision is only 1 second, while time() and os.clock() give milliseconds like tick() without the float error.

Also note that time() is not the same as os.time(). time() is actually similar to os.clock(), but time starts as the game starts (always 0 when in edit mode), while os.clock() starts when Lua starts (opening Studio).

@YasuYoshida , that wait() should be replaced with a heartbeat:Wait() loop as it can wait for anything over 4 seconds, even 10 years if the thread pool is full.

6 Likes

I know the code isn’t running but the actual remote is running. Could that still lag the server even if the code is running firing the remote itself multiple times could actually make the server lag

4 Likes

This is correct and something a lot of developers look over.

The example Yasu showed is actually not that good because the remote connection can still get fired along with the if statement check:

Even if you disconnect the event, the Remote is still sitting there (Or any other unscripted RemoteEvent for that matter) and can still be called, if spammed enough the Server will drop the connections due to built in spam prevention but this still means it’s taking up computational resources because this error is provoked server side:

The only real solution I have found which sounds ludicrous is to create a separate Remote for every player in the server. Then the players simply call their own so called personal Remote, you might want to kick the exploiters calling a wrong players remote though. These remotes, depending on which ones are called are then completely removed from the game and re-instanced once your main loop cooldown has expired.

1 Like

Seems inefficient that would mean if you had like 15 remotes and a 20 player lobby thats 300 remotes. U can use a table of the player that fired the remote store a value of os.time() check for when it was last called or add more checks to see how many times it was called by the player

i would suggest a global table tho instead of making a new table per remote maybe table.Player.Remote.Data

There’s also another method - just do a max objects per 5 seconds. This way you could allow the player to spawn some objects without having to wait and prevents people who crash servers with it too

(Sorry for bumping)

This is awesome!

I needed it to function as a Module Script. I updated it for anyone that wants it:

MODULE:

local module = {}

	local playerTable = {}

	function module.AddPlayer(player)
		if not playerTable[player.Name] then
			playerTable[player.Name] = tick()
		end
	end

	function module.RemovePlayer(player)
		if playerTable[player.Name] then
			playerTable[player.Name] = nil
		end
	end

	function module.ResetTick(player)
		playerTable[player.Name] = tick()
	end

	function module.ReturnTick(player, cooldown)
		local currentTick = tick() - playerTable[player.Name] > cooldown
		module.ResetTick(player)
		return currentTick
	end

return module

PLAYER ADDED:

Players.PlayerAdded:Connect(function(player)
	sssModule_Tick.AddPlayer(player)
end)

PLAYER REMOVING:

Players.PlayerRemoving:Connect(function(player)
	sssModule_Tick.RemovePlayer(player)
end)

CALL FROM A SCRIPT:

local check = tickModule.ReturnTick(player, 2) -- number is time you want to wait
if check == true then
	-- do something
end
1 Like

Does doing this impact performance?

what if we control all RemoteEvents in the game?

How would you make the same thing but for every remote in the game without connecting every remote event by hand. Maybe like with a table or something?

you could use a wrapper module that listens for processed requests that has been checked before-hand

example:

local function listenToEvent(remoteName, callback)
   local remote = Remotes:WaitForChild(remoteName)

   remote.OnServerEvent:Connect(function(player, ...)
      if isPlayerAllowed(player) then callback(player, ...)
   end)
end

-- usage

listenToEvent('helloWorld', function(player)
   print(`{player.Name} fired the hello world remote omg real!!!!`)
end)

you still need to connect the remote by hand tho, but without the hassle of copy pasting the check